From 48e627532b593f686f559f662538d960330b7987 Mon Sep 17 00:00:00 2001 From: Mina Naguib Date: Wed, 20 Sep 2023 14:22:40 -0400 Subject: [PATCH 001/138] DOOH support (#2758) --- adapters/infoawarebidder.go | 24 +- adapters/infoawarebidder_test.go | 30 ++- config/account.go | 6 +- config/account_test.go | 21 +- config/bidderinfo.go | 24 +- config/bidderinfo_test.go | 61 ++++- .../test/bidder-info-valid/stroeerCore.yaml | 4 + endpoints/info/bidders_detail.go | 7 + endpoints/info/bidders_detail_test.go | 2 + endpoints/openrtb2/auction.go | 92 ++++++-- endpoints/openrtb2/auction_test.go | 209 +++++++++++++++++- ...r-app.json => no-site-or-app-or-dooh.json} | 2 +- .../invalid-whole/site-app-both.json | 4 +- .../invalid-whole/site-dooh-both.json | 27 +++ .../valid-whole/exemplary/dooh.json | 53 +++++ exchange/utils.go | 9 +- metrics/metrics.go | 12 +- openrtb_ext/request_wrapper.go | 99 +++++++++ openrtb_ext/request_wrapper_test.go | 140 ++++++++++++ 19 files changed, 774 insertions(+), 52 deletions(-) rename endpoints/openrtb2/sample-requests/invalid-whole/{no-site-or-app.json => no-site-or-app-or-dooh.json} (85%) create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/site-dooh-both.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/dooh.json diff --git a/adapters/infoawarebidder.go b/adapters/infoawarebidder.go index 321a87a3bec..538a46ae05c 100644 --- a/adapters/infoawarebidder.go +++ b/adapters/infoawarebidder.go @@ -13,7 +13,7 @@ import ( // media types defined in the static/bidder-info/{bidder}.yaml file. // // It adjusts incoming requests in the following ways: -// 1. If App or Site traffic is not supported by the info file, then requests from +// 1. If App, Site or DOOH traffic is not supported by the info file, then requests from // those sources will be rejected before the delegate is called. // 2. If a given MediaType is not supported for the platform, then it will be set // to nil before the request is forwarded to the delegate. @@ -24,7 +24,7 @@ type InfoAwareBidder struct { info parsedBidderInfo } -// BuildInfoAwareBidder wraps a bidder to enforce site, app, and media type support. +// BuildInfoAwareBidder wraps a bidder to enforce inventory {site, app, dooh} and media type support. func BuildInfoAwareBidder(bidder Bidder, info config.BidderInfo) Bidder { return &InfoAwareBidder{ Bidder: bidder, @@ -47,6 +47,12 @@ func (i *InfoAwareBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *Ex } allowedMediaTypes = i.info.app } + if request.DOOH != nil { + if !i.info.dooh.enabled { + return nil, []error{&errortypes.Warning{Message: "this bidder does not support dooh requests"}} + } + allowedMediaTypes = i.info.dooh + } // Filtering imps is quite expensive (array filter with large, non-pointer elements)... but should be rare, // because it only happens if the publisher makes a really bad request. @@ -136,6 +142,7 @@ func filterImps(imps []openrtb2.Imp, numToFilter int) ([]openrtb2.Imp, []error) type parsedBidderInfo struct { app parsedSupports site parsedSupports + dooh parsedSupports } type parsedSupports struct { @@ -148,13 +155,22 @@ type parsedSupports struct { func parseBidderInfo(info config.BidderInfo) parsedBidderInfo { var parsedInfo parsedBidderInfo - if info.Capabilities != nil && info.Capabilities.App != nil { + + if info.Capabilities == nil { + return parsedInfo + } + + if info.Capabilities.App != nil { parsedInfo.app.enabled = true parsedInfo.app.banner, parsedInfo.app.video, parsedInfo.app.audio, parsedInfo.app.native = parseAllowedTypes(info.Capabilities.App.MediaTypes) } - if info.Capabilities != nil && info.Capabilities.Site != nil { + if info.Capabilities.Site != nil { parsedInfo.site.enabled = true parsedInfo.site.banner, parsedInfo.site.video, parsedInfo.site.audio, parsedInfo.site.native = parseAllowedTypes(info.Capabilities.Site.MediaTypes) } + if info.Capabilities.DOOH != nil { + parsedInfo.dooh.enabled = true + parsedInfo.dooh.banner, parsedInfo.dooh.video, parsedInfo.dooh.audio, parsedInfo.dooh.native = parseAllowedTypes(info.Capabilities.DOOH.MediaTypes) + } return parsedInfo } diff --git a/adapters/infoawarebidder_test.go b/adapters/infoawarebidder_test.go index f42716bc1d6..87d9d4d442a 100644 --- a/adapters/infoawarebidder_test.go +++ b/adapters/infoawarebidder_test.go @@ -10,6 +10,7 @@ import ( "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestAppNotSupported(t *testing.T) { @@ -56,6 +57,26 @@ func TestSiteNotSupported(t *testing.T) { assert.Len(t, bids, 0) } +func TestDOOHNotSupported(t *testing.T) { + bidder := &mockBidder{} + info := config.BidderInfo{ + Capabilities: &config.CapabilitiesInfo{ + Site: &config.PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, + }, + } + constrained := adapters.BuildInfoAwareBidder(bidder, info) + bids, errs := constrained.MakeRequests(&openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ID: "imp-1", Banner: &openrtb2.Banner{}}}, + DOOH: &openrtb2.DOOH{}, + }, &adapters.ExtraRequestInfo{}) + require.Len(t, errs, 1) + assert.EqualError(t, errs[0], "this bidder does not support dooh requests") + assert.IsType(t, &errortypes.Warning{}, errs[0]) + assert.Len(t, bids, 0) +} + func TestImpFiltering(t *testing.T) { bidder := &mockBidder{} info := config.BidderInfo{ @@ -66,6 +87,9 @@ func TestImpFiltering(t *testing.T) { App: &config.PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, }, + DOOH: &config.PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeNative}, + }, }, } @@ -153,10 +177,10 @@ func TestImpFiltering(t *testing.T) { description: "All imps with correct media type, MakeRequest() call expected", inBidRequest: &openrtb2.BidRequest{ Imp: []openrtb2.Imp{ - {ID: "imp-1", Video: &openrtb2.Video{}}, - {ID: "imp-2", Video: &openrtb2.Video{}}, + {ID: "imp-1", Native: &openrtb2.Native{}}, + {ID: "imp-2", Native: &openrtb2.Native{}}, }, - Site: &openrtb2.Site{}, + DOOH: &openrtb2.DOOH{}, }, expectedErrors: nil, expectedImpLen: 2, diff --git a/config/account.go b/config/account.go index e5265072164..4b0ceeef0cf 100644 --- a/config/account.go +++ b/config/account.go @@ -20,6 +20,7 @@ const ( ChannelApp ChannelType = "app" ChannelVideo ChannelType = "video" ChannelWeb ChannelType = "web" + ChannelDOOH ChannelType = "dooh" ) // Account represents a publisher account configuration @@ -251,6 +252,7 @@ type AccountChannel struct { App *bool `mapstructure:"app" json:"app,omitempty"` Video *bool `mapstructure:"video" json:"video,omitempty"` Web *bool `mapstructure:"web" json:"web,omitempty"` + DOOH *bool `mapstructure:"dooh" json:"dooh,omitempty"` } // GetByChannelType looks up the account integration enabled setting for the specified channel type @@ -266,6 +268,8 @@ func (a *AccountChannel) GetByChannelType(channelType ChannelType) *bool { channelEnabled = a.Video case ChannelWeb: channelEnabled = a.Web + case ChannelDOOH: + channelEnabled = a.DOOH } return channelEnabled @@ -296,7 +300,7 @@ func (m AccountModules) ModuleConfig(id string) (json.RawMessage, error) { } func (a *AccountChannel) IsSet() bool { - return a.AMP != nil || a.App != nil || a.Video != nil || a.Web != nil + return a.AMP != nil || a.App != nil || a.Video != nil || a.Web != nil || a.DOOH != nil } type AccountPrivacy struct { diff --git a/config/account_test.go b/config/account_test.go index 31d9796a622..58f2a52e645 100644 --- a/config/account_test.go +++ b/config/account_test.go @@ -205,6 +205,7 @@ func TestAccountChannelGetByChannelType(t *testing.T) { giveAppEnabled *bool giveVideoEnabled *bool giveWebEnabled *bool + giveDOOHEnabled *bool giveChannelType ChannelType wantEnabled *bool }{ @@ -276,6 +277,23 @@ func TestAccountChannelGetByChannelType(t *testing.T) { giveChannelType: ChannelWeb, wantEnabled: &trueValue, }, + { + description: "DOOH channel setting unspecified, returns nil", + giveChannelType: ChannelDOOH, + wantEnabled: nil, + }, + { + description: "DOOH channel disabled, returns false", + giveDOOHEnabled: &falseValue, + giveChannelType: ChannelDOOH, + wantEnabled: &falseValue, + }, + { + description: "DOOH channel enabled, returns true", + giveDOOHEnabled: &trueValue, + giveChannelType: ChannelDOOH, + wantEnabled: &trueValue, + }, } for _, tt := range tests { @@ -284,6 +302,7 @@ func TestAccountChannelGetByChannelType(t *testing.T) { App: tt.giveAppEnabled, Video: tt.giveVideoEnabled, Web: tt.giveWebEnabled, + DOOH: tt.giveDOOHEnabled, } result := accountChannel.GetByChannelType(tt.giveChannelType) @@ -836,7 +855,7 @@ func TestAccountChannelIsSet(t *testing.T) { }{ { name: "AccountChannelSetAllFields", - givenAccountChannel: &AccountChannel{AMP: &trueBool, App: &falseBool, Video: &falseBool, Web: &falseBool}, + givenAccountChannel: &AccountChannel{AMP: &trueBool, App: &falseBool, Video: &falseBool, Web: &falseBool, DOOH: &falseBool}, expected: true, }, { diff --git a/config/bidderinfo.go b/config/bidderinfo.go index 96d6fd15bfc..0027e4d21d0 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -78,6 +78,7 @@ type MaintainerInfo struct { type CapabilitiesInfo struct { App *PlatformInfo `yaml:"app" mapstructure:"app"` Site *PlatformInfo `yaml:"site" mapstructure:"site"` + DOOH *PlatformInfo `yaml:"dooh" mapstructure:"dooh"` } // PlatformInfo specifies the supported media types for a bidder. @@ -461,7 +462,9 @@ func validateAliasCapabilities(aliasBidderInfo BidderInfo, infos BidderInfos, bi return fmt.Errorf("capabilities for alias: %s should be a subset of capabilities for parent bidder: %s", bidderName, aliasBidderInfo.AliasOf) } - if (aliasBidderInfo.Capabilities.App != nil && parentBidder.Capabilities.App == nil) || (aliasBidderInfo.Capabilities.Site != nil && parentBidder.Capabilities.Site == nil) { + if (aliasBidderInfo.Capabilities.App != nil && parentBidder.Capabilities.App == nil) || + (aliasBidderInfo.Capabilities.Site != nil && parentBidder.Capabilities.Site == nil) || + (aliasBidderInfo.Capabilities.DOOH != nil && parentBidder.Capabilities.DOOH == nil) { return fmt.Errorf("capabilities for alias: %s should be a subset of capabilities for parent bidder: %s", bidderName, aliasBidderInfo.AliasOf) } @@ -476,6 +479,12 @@ func validateAliasCapabilities(aliasBidderInfo BidderInfo, infos BidderInfos, bi return err } } + + if aliasBidderInfo.Capabilities.DOOH != nil && parentBidder.Capabilities.DOOH != nil { + if err := isAliasPlatformInfoSubsetOfParent(*parentBidder.Capabilities.DOOH, *aliasBidderInfo.Capabilities.DOOH, bidderName, aliasBidderInfo.AliasOf); err != nil { + return err + } + } } return nil @@ -501,8 +510,8 @@ func validateCapabilities(info *CapabilitiesInfo, bidderName string) error { return fmt.Errorf("missing required field: capabilities for adapter: %s", bidderName) } - if info.App == nil && info.Site == nil { - return fmt.Errorf("at least one of capabilities.site or capabilities.app must exist for adapter: %s", bidderName) + if info.App == nil && info.Site == nil && info.DOOH == nil { + return fmt.Errorf("at least one of capabilities.site, capabilities.app, or capabilities.dooh must exist for adapter: %s", bidderName) } if info.App != nil { @@ -513,9 +522,16 @@ func validateCapabilities(info *CapabilitiesInfo, bidderName string) error { if info.Site != nil { if err := validatePlatformInfo(info.Site); err != nil { - return fmt.Errorf("capabilities.site failed validation: %v, for adapter: %s", err, bidderName) + return fmt.Errorf("capabilities.site failed validation: %v for adapter: %s", err, bidderName) } } + + if info.DOOH != nil { + if err := validatePlatformInfo(info.DOOH); err != nil { + return fmt.Errorf("capabilities.dooh failed validation: %v for adapter: %s", err, bidderName) + } + } + return nil } diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go index 77625c0cdd0..438ed5af776 100644 --- a/config/bidderinfo_test.go +++ b/config/bidderinfo_test.go @@ -31,6 +31,9 @@ capabilities: - banner - video - native + dooh: + mediaTypes: + - banner modifyingVastXmlAllowed: true debug: allow: true @@ -92,6 +95,9 @@ func TestLoadBidderInfoFromDisk(t *testing.T) { Site: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, }, + DOOH: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo}, + }, }, Syncer: &Syncer{ Key: "foo", @@ -176,6 +182,9 @@ func TestProcessBidderInfo(t *testing.T) { Site: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, }, + DOOH: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, }, Debug: &DebugInfo{ Allow: true, @@ -225,6 +234,9 @@ func TestProcessBidderInfo(t *testing.T) { Site: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, }, + DOOH: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, }, Debug: &DebugInfo{ Allow: true, @@ -533,7 +545,7 @@ func TestBidderInfoValidationPositive(t *testing.T) { Endpoint: "http://bidderB.com/openrtb2", PlatformID: "B", Maintainer: &MaintainerInfo{ - Email: "maintainer@bidderA.com", + Email: "maintainer@bidderB.com", }, GVLVendorID: 2, Capabilities: &CapabilitiesInfo{ @@ -569,6 +581,23 @@ func TestBidderInfoValidationPositive(t *testing.T) { }, AliasOf: "bidderB", }, + "bidderD": BidderInfo{ + Endpoint: "http://bidderD.com/openrtb2", + PlatformID: "D", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderD.com", + }, + GVLVendorID: 3, + Capabilities: &CapabilitiesInfo{ + DOOH: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + openrtb_ext.BidTypeNative, + openrtb_ext.BidTypeBanner, + }, + }, + }, + }, } errs := bidderInfos.validate(make([]error, 0)) assert.Len(t, errs, 0, "All bidder infos should be correct") @@ -769,7 +798,7 @@ func TestBidderInfoValidationNegative(t *testing.T) { }, }, { - "One bidder missing capabilities site and app", + "One bidder missing capabilities site and app and dooh", BidderInfos{ "bidderA": BidderInfo{ Endpoint: "http://bidderA.com/openrtb2", @@ -780,7 +809,7 @@ func TestBidderInfoValidationNegative(t *testing.T) { }, }, []error{ - errors.New("at least one of capabilities.site or capabilities.app must exist for adapter: bidderA"), + errors.New("at least one of capabilities.site, capabilities.app, or capabilities.dooh must exist for adapter: bidderA"), }, }, { @@ -804,6 +833,27 @@ func TestBidderInfoValidationNegative(t *testing.T) { errors.New("capabilities.app failed validation: unrecognized media type at index 0: incorrect for adapter: bidderA"), }, }, + { + "One bidder incorrect capabilities for dooh", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + DOOH: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + "incorrect", + }, + }, + }, + }, + }, + []error{ + errors.New("capabilities.dooh failed validation: unrecognized media type at index 0: incorrect for adapter: bidderA"), + }, + }, { "One bidder nil capabilities", BidderInfos{ @@ -1008,7 +1058,7 @@ func TestBidderInfoValidationNegative(t *testing.T) { }, }, []error{ - errors.New("at least one of capabilities.site or capabilities.app must exist for adapter: bidderA"), + errors.New("at least one of capabilities.site, capabilities.app, or capabilities.dooh must exist for adapter: bidderA"), errors.New("capabilities for alias: bidderB should be a subset of capabilities for parent bidder: bidderA"), }, }, @@ -1687,6 +1737,9 @@ func TestReadFullYamlBidderConfig(t *testing.T) { Site: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, }, + DOOH: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, }, ModifyingVastXmlAllowed: true, Debug: &DebugInfo{ diff --git a/config/test/bidder-info-valid/stroeerCore.yaml b/config/test/bidder-info-valid/stroeerCore.yaml index 0a1b3059cd4..ef95d2388a5 100644 --- a/config/test/bidder-info-valid/stroeerCore.yaml +++ b/config/test/bidder-info-valid/stroeerCore.yaml @@ -11,6 +11,10 @@ capabilities: - banner - video - native + dooh: + mediaTypes: + - banner + - video userSync: key: "foo" default: "iframe" diff --git a/endpoints/info/bidders_detail.go b/endpoints/info/bidders_detail.go index 1446e3ac22a..9b8d42686ae 100644 --- a/endpoints/info/bidders_detail.go +++ b/endpoints/info/bidders_detail.go @@ -122,6 +122,7 @@ type maintainer struct { type capabilities struct { App *platform `json:"app,omitempty"` Site *platform `json:"site,omitempty"` + DOOH *platform `json:"dooh,omitempty"` } type platform struct { @@ -157,6 +158,12 @@ func mapDetailFromConfig(c config.BidderInfo) bidderDetail { MediaTypes: mapMediaTypes(c.Capabilities.Site.MediaTypes), } } + + if c.Capabilities.DOOH != nil { + bidderDetail.Capabilities.DOOH = &platform{ + MediaTypes: mapMediaTypes(c.Capabilities.DOOH.MediaTypes), + } + } } } else { bidderDetail.Status = statusDisabled diff --git a/endpoints/info/bidders_detail_test.go b/endpoints/info/bidders_detail_test.go index 47e5a0688a8..3be24acfdba 100644 --- a/endpoints/info/bidders_detail_test.go +++ b/endpoints/info/bidders_detail_test.go @@ -225,6 +225,7 @@ func TestMapDetailFromConfig(t *testing.T) { Capabilities: &config.CapabilitiesInfo{ App: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}, Site: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}, + DOOH: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeNative}}, }, }, expected: bidderDetail{ @@ -236,6 +237,7 @@ func TestMapDetailFromConfig(t *testing.T) { Capabilities: &capabilities{ App: &platform{MediaTypes: []string{"banner"}}, Site: &platform{MediaTypes: []string{"video"}}, + DOOH: &platform{MediaTypes: []string{"native"}}, }, AliasOf: "", }, diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index aeb5c20e64e..375756cc565 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -69,13 +69,16 @@ var ( ) var accountIdSearchPath = [...]struct { - isApp bool - key []string + isApp bool + isDOOH bool + key []string }{ - {true, []string{"app", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, - {true, []string{"app", "publisher", "id"}}, - {false, []string{"site", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, - {false, []string{"site", "publisher", "id"}}, + {true, false, []string{"app", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, + {true, false, []string{"app", "publisher", "id"}}, + {false, false, []string{"site", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, + {false, false, []string{"site", "publisher", "id"}}, + {false, true, []string{"dooh", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, + {false, true, []string{"dooh", "publisher", "id"}}, } func NewEndpoint( @@ -457,12 +460,16 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric return } - accountId, isAppReq, errs := getAccountIdFromRawRequest(hasStoredBidRequest, storedRequests[storedBidRequestId], requestJson) + accountId, isAppReq, isDOOHReq, errs := getAccountIdFromRawRequest(hasStoredBidRequest, storedRequests[storedBidRequestId], requestJson) // fill labels here in order to pass correct metrics in case of errors if isAppReq { labels.Source = metrics.DemandApp labels.RType = metrics.ReqTypeORTB2App labels.PubID = accountId + } else if isDOOHReq { + labels.Source = metrics.DemandDOOH + labels.RType = metrics.ReqTypeORTB2DOOH + labels.PubID = accountId } else { // is Site request labels.Source = metrics.DemandWeb labels.PubID = accountId @@ -804,8 +811,8 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp return []error{err} } - if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) { - return append(errL, errors.New("request.site or request.app must be defined, but not both.")) + if err := validateExactlyOneInventoryType(req); err != nil { + return []error{err} } if errs := validateRequestExt(req); len(errs) != 0 { @@ -823,6 +830,9 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp return append(errL, err) } + if err := deps.validateDOOH(req); err != nil { + return append(errL, err) + } var gpp gpplib.GppContainer if req.BidRequest.Regs != nil && len(req.BidRequest.Regs.GPP) > 0 { gpp, err = gpplib.Parse(req.BidRequest.Regs.GPP) @@ -1747,6 +1757,18 @@ func (deps *endpointDeps) validateApp(req *openrtb_ext.RequestWrapper) error { return err } +func (deps *endpointDeps) validateDOOH(req *openrtb_ext.RequestWrapper) error { + if req.DOOH == nil { + return nil + } + + if req.DOOH.ID == "" && len(req.DOOH.VenueType) == 0 { + return errors.New("request.dooh should include at least one of request.dooh.id or request.dooh.venuetype.") + } + + return nil +} + func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases map[string]string, gpp gpplib.GppContainer) []error { var errL []error @@ -1872,6 +1894,30 @@ func validateDevice(device *openrtb2.Device) error { return nil } +func validateExactlyOneInventoryType(reqWrapper *openrtb_ext.RequestWrapper) error { + + // Prep for mutual exclusion check + invTypeNumMatches := 0 + if reqWrapper.Site != nil { + invTypeNumMatches++ + } + if reqWrapper.App != nil { + invTypeNumMatches++ + } + if reqWrapper.DOOH != nil { + invTypeNumMatches++ + } + + if invTypeNumMatches == 0 { + return errors.New("One of request.site or request.app or request.dooh must be defined") + } else if invTypeNumMatches >= 2 { + return errors.New("No more than one of request.site or request.app or request.dooh can be defined") + } else { + return nil + } + +} + func validateOrFillChannel(reqWrapper *openrtb_ext.RequestWrapper, isAmp bool) error { requestExt, err := reqWrapper.GetRequestExt() if err != nil { @@ -1934,9 +1980,9 @@ func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, r *openrtb_ setDeviceImplicitly(httpReq, r, deps.privateNetworkIPValidator) - // Per the OpenRTB spec: A bid request must not contain both a Site and an App object. If neither are - // present, we'll assume it's a site request. - if r.App == nil { + // Per the OpenRTB spec: A bid request must not contain more than one of Site|App|DOOH + // Assume it's a site request if it's not declared as one of the other values + if r.App == nil && r.DOOH == nil { setSiteImplicitly(httpReq, r) } @@ -2325,43 +2371,43 @@ func getAccountID(pub *openrtb2.Publisher) string { return metrics.PublisherUnknown } -func getAccountIdFromRawRequest(hasStoredRequest bool, storedRequest json.RawMessage, originalRequest []byte) (string, bool, []error) { +func getAccountIdFromRawRequest(hasStoredRequest bool, storedRequest json.RawMessage, originalRequest []byte) (string, bool, bool, []error) { request := originalRequest if hasStoredRequest { request = storedRequest } - accountId, isAppReq, err := searchAccountId(request) + accountId, isAppReq, isDOOHReq, err := searchAccountId(request) if err != nil { - return "", isAppReq, []error{err} + return "", isAppReq, isDOOHReq, []error{err} } // In case the stored request did not have account data we specifically search it in the original request if accountId == "" && hasStoredRequest { - accountId, _, err = searchAccountId(originalRequest) + accountId, _, _, err = searchAccountId(originalRequest) if err != nil { - return "", isAppReq, []error{err} + return "", isAppReq, isDOOHReq, []error{err} } } if accountId == "" { - return metrics.PublisherUnknown, isAppReq, nil + return metrics.PublisherUnknown, isAppReq, isDOOHReq, nil } - return accountId, isAppReq, nil + return accountId, isAppReq, isDOOHReq, nil } -func searchAccountId(request []byte) (string, bool, error) { +func searchAccountId(request []byte) (string, bool, bool, error) { for _, path := range accountIdSearchPath { accountId, exists, err := getStringValueFromRequest(request, path.key) if err != nil { - return "", path.isApp, err + return "", path.isApp, path.isDOOH, err } if exists { - return accountId, path.isApp, nil + return accountId, path.isApp, path.isDOOH, nil } } - return "", false, nil + return "", false, false, nil } func getStringValueFromRequest(request []byte, key []string) (string, bool, error) { diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 84e5cf4baf5..f60fd98e926 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -205,7 +205,7 @@ func runEndToEndTest(t *testing.T, auctionEndpointHandler httprouter.Handle, tes err = json.Unmarshal(test.ExpectedBidResponse, &expectedBidResponse) if assert.NoError(t, err, "Could not unmarshal expected bidResponse taken from test file.\n Test file: %s\n Error:%s\n", testFile, err) { err = json.Unmarshal([]byte(actualJsonBidResponse), &actualBidResponse) - if assert.NoError(t, err, "Could not unmarshal actual bidResponse from auction.\n Test file: %s\n Error:%s\n", testFile, err) { + if assert.NoError(t, err, "Could not unmarshal actual bidResponse from auction.\n Test file: %s\n Error:%s\n actualJsonBidResponse: %s", testFile, err, actualJsonBidResponse) { assertBidResponseEqual(t, testFile, expectedBidResponse, actualBidResponse) } } @@ -1541,6 +1541,81 @@ func TestMergeBidderParamsImpExtPrebid(t *testing.T) { } } +func TestValidateExactlyOneInventoryType(t *testing.T) { + + testCases := []struct { + description string + givenRequestWrapper *openrtb_ext.RequestWrapper + expectedError error + }{ + { + description: "None provided - invalid", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}, + expectedError: errors.New("One of request.site or request.app or request.dooh must be defined"), + }, + { + description: "Only site provided", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{}, + }}, + expectedError: nil, + }, + { + description: "Only app provided", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + }}, + expectedError: nil, + }, + { + description: "Only dooh provided", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + DOOH: &openrtb2.DOOH{}, + }}, + expectedError: nil, + }, + { + description: "Two provided (site+app) - invalid", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{}, + App: &openrtb2.App{}, + }}, + expectedError: errors.New("No more than one of request.site or request.app or request.dooh can be defined"), + }, + { + description: "Two provided (site+dooh) - invalid", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{}, + DOOH: &openrtb2.DOOH{}, + }}, + expectedError: errors.New("No more than one of request.site or request.app or request.dooh can be defined"), + }, + { + description: "Two provided (app+dooh) - invalid", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + DOOH: &openrtb2.DOOH{}, + }}, + expectedError: errors.New("No more than one of request.site or request.app or request.dooh can be defined"), + }, + { + description: "Three provided - invalid", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{}, + App: &openrtb2.App{}, + DOOH: &openrtb2.DOOH{}, + }}, + expectedError: errors.New("No more than one of request.site or request.app or request.dooh can be defined"), + }, + } + + for _, test := range testCases { + error := validateExactlyOneInventoryType(test.givenRequestWrapper) + assert.Equalf(t, test.expectedError, error, "Error doesn't match: %s\n", test.description) + } + +} + func TestValidateRequest(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, @@ -1725,6 +1800,66 @@ func TestValidateRequest(t *testing.T) { expectedErrorList: []error{}, expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, }, + { + description: "Minimum required site attributes missing", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "Some-ID", + Site: &openrtb2.Site{}, + Imp: []openrtb2.Imp{ + { + ID: "Some-Imp-ID", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 600, + H: 500, + }, + { + W: 300, + H: 600, + }, + }, + }, + Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), + }, + }, + }, + }, + expectedErrorList: []error{ + errors.New("request.site should include at least one of request.site.id or request.site.page."), + }, + }, + { + description: "Minimum required DOOH attributes missing", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "Some-ID", + DOOH: &openrtb2.DOOH{}, + Imp: []openrtb2.Imp{ + { + ID: "Some-Imp-ID", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 600, + H: 500, + }, + { + W: 300, + H: 600, + }, + }, + }, + Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), + }, + }, + }, + }, + expectedErrorList: []error{ + errors.New("request.dooh should include at least one of request.dooh.id or request.dooh.venuetype."), + }, + }, } for _, test := range testCases { @@ -3237,6 +3372,78 @@ func TestMapSChains(t *testing.T) { } } +func TestSearchAccountID(t *testing.T) { + // Correctness for lookup within Publisher object left to TestGetAccountID + // This however tests the expected lookup paths in outer site, app and dooh + testCases := []struct { + description string + request []byte + expectedAccID string + expectedError error + expectedIsAppReq bool + expectedIsSiteReq bool + expectedIsDOOHReq bool + }{ + { + description: "No publisher available", + request: []byte(`{}`), + expectedAccID: "", + expectedError: nil, + expectedIsAppReq: false, + expectedIsDOOHReq: false, + }, + { + description: "Publisher.ID doesn't exist", + request: []byte(`{"site":{"publisher":{}}}`), + expectedAccID: "", + expectedError: nil, + expectedIsAppReq: false, + expectedIsDOOHReq: false, + }, + { + description: "Publisher.ID not a string", + request: []byte(`{"site":{"publisher":{"id":42}}}`), + expectedAccID: "", + expectedError: errors.New("site.publisher.id must be a string"), + expectedIsAppReq: false, + expectedIsDOOHReq: false, + }, + { + description: "Publisher available in request.site", + request: []byte(`{"site":{"publisher":{"id":"42"}}}`), + expectedAccID: "42", + expectedError: nil, + expectedIsAppReq: false, + expectedIsDOOHReq: false, + }, + { + description: "Publisher available in request.app", + request: []byte(`{"app":{"publisher":{"id":"42"}}}`), + expectedAccID: "42", + expectedError: nil, + expectedIsAppReq: true, + expectedIsDOOHReq: false, + }, + { + description: "Publisher available in request.dooh", + request: []byte(`{"dooh":{"publisher":{"id":"42"}}}`), + expectedAccID: "42", + expectedError: nil, + expectedIsAppReq: false, + expectedIsDOOHReq: true, + }, + } + + for _, test := range testCases { + accountId, isAppReq, isDOOHReq, err := searchAccountId(test.request) + assert.Equal(t, test.expectedAccID, accountId, "searchAccountID should return expected account ID for test case: %s", test.description) + assert.Equal(t, test.expectedIsAppReq, isAppReq, "searchAccountID should return expected isAppReq for test case: %s", test.description) + assert.Equal(t, test.expectedIsDOOHReq, isDOOHReq, "searchAccountID should return expected isDOOHReq for test case: %s", test.description) + assert.Equal(t, test.expectedError, err, "searchAccountID should return expected error for test case: %s", test.description) + } + +} + func TestGetAccountID(t *testing.T) { testPubID := "test-pub" testParentAccount := "test-account" diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/no-site-or-app.json b/endpoints/openrtb2/sample-requests/invalid-whole/no-site-or-app-or-dooh.json similarity index 85% rename from endpoints/openrtb2/sample-requests/invalid-whole/no-site-or-app.json rename to endpoints/openrtb2/sample-requests/invalid-whole/no-site-or-app-or-dooh.json index 8ec2273ebec..c883979c98d 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/no-site-or-app.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/no-site-or-app-or-dooh.json @@ -1,5 +1,5 @@ { - "description": "Request does not come with site field nor app field", + "description": "Request does not come with site nor app nor dooh field", "mockBidRequest": { "id": "req-id", "imp": [ diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/site-app-both.json b/endpoints/openrtb2/sample-requests/invalid-whole/site-app-both.json index 5bc3054c356..255ea59e498 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/site-app-both.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/site-app-both.json @@ -1,5 +1,5 @@ { - "description": "Bid request comes with both site and app fields, it should only come with one or the other", + "description": "Bid request comes with both site and app fields, it should only come with one", "mockBidRequest": { "id": "req-id", "site": { @@ -23,5 +23,5 @@ ] }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.site or request.app must be defined, but not both.\n" + "expectedErrorMessage": "Invalid request: No more than one of request.site or request.app or request.dooh can be defined\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/site-dooh-both.json b/endpoints/openrtb2/sample-requests/invalid-whole/site-dooh-both.json new file mode 100644 index 00000000000..d4760d12a99 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/site-dooh-both.json @@ -0,0 +1,27 @@ +{ + "description": "Bid request comes with both site and dooh fields, it should only come with one", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "test.mysite.com" + }, + "dooh": {}, + "imp": [ + { + "id": "imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: No more than one of request.site or request.app or request.dooh can be defined\n" +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/dooh.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/dooh.json new file mode 100644 index 00000000000..faa9a7f6646 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/dooh.json @@ -0,0 +1,53 @@ +{ + "description": "Simple DOOH request", + "config": { + "mockBidders": [ + {"bidderName": "appnexus", "currency": "USD", "price": 0.00} + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "dooh": { + "id": "12345" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 1920, + "h": 1080 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": {} + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0 + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} diff --git a/exchange/utils.go b/exchange/utils.go index 0becca21f6c..6c47bcaa9c0 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -29,10 +29,11 @@ import ( ) var channelTypeMap = map[metrics.RequestType]config.ChannelType{ - metrics.ReqTypeAMP: config.ChannelAMP, - metrics.ReqTypeORTB2App: config.ChannelApp, - metrics.ReqTypeVideo: config.ChannelVideo, - metrics.ReqTypeORTB2Web: config.ChannelWeb, + metrics.ReqTypeAMP: config.ChannelAMP, + metrics.ReqTypeORTB2App: config.ChannelApp, + metrics.ReqTypeVideo: config.ChannelVideo, + metrics.ReqTypeORTB2Web: config.ChannelWeb, + metrics.ReqTypeORTB2DOOH: config.ChannelDOOH, } const unknownBidder string = "" diff --git a/metrics/metrics.go b/metrics/metrics.go index c30f81675a0..d39c06d7869 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -164,6 +164,7 @@ const PublisherUnknown = "unknown" const ( DemandWeb DemandSource = "web" DemandApp DemandSource = "app" + DemandDOOH DemandSource = "dooh" DemandUnknown DemandSource = "unknown" ) @@ -171,22 +172,25 @@ func DemandTypes() []DemandSource { return []DemandSource{ DemandWeb, DemandApp, + DemandDOOH, DemandUnknown, } } // The request types (endpoints) const ( - ReqTypeORTB2Web RequestType = "openrtb2-web" - ReqTypeORTB2App RequestType = "openrtb2-app" - ReqTypeAMP RequestType = "amp" - ReqTypeVideo RequestType = "video" + ReqTypeORTB2Web RequestType = "openrtb2-web" + ReqTypeORTB2App RequestType = "openrtb2-app" + ReqTypeORTB2DOOH RequestType = "openrtb2-dooh" + ReqTypeAMP RequestType = "amp" + ReqTypeVideo RequestType = "video" ) func RequestTypes() []RequestType { return []RequestType{ ReqTypeORTB2Web, ReqTypeORTB2App, + ReqTypeORTB2DOOH, ReqTypeAMP, ReqTypeVideo, } diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go index 5f5636d602a..f4ef69b0523 100644 --- a/openrtb_ext/request_wrapper.go +++ b/openrtb_ext/request_wrapper.go @@ -42,6 +42,7 @@ type RequestWrapper struct { appExt *AppExt regExt *RegExt siteExt *SiteExt + doohExt *DOOHExt sourceExt *SourceExt } @@ -160,6 +161,17 @@ func (rw *RequestWrapper) GetSiteExt() (*SiteExt, error) { return rw.siteExt, rw.siteExt.unmarshal(rw.Site.Ext) } +func (rw *RequestWrapper) GetDOOHExt() (*DOOHExt, error) { + if rw.doohExt != nil { + return rw.doohExt, nil + } + rw.doohExt = &DOOHExt{} + if rw.BidRequest == nil || rw.DOOH == nil || rw.DOOH.Ext == nil { + return rw.doohExt, rw.doohExt.unmarshal(json.RawMessage{}) + } + return rw.doohExt, rw.doohExt.unmarshal(rw.DOOH.Ext) +} + func (rw *RequestWrapper) GetSourceExt() (*SourceExt, error) { if rw.sourceExt != nil { return rw.sourceExt, nil @@ -197,6 +209,9 @@ func (rw *RequestWrapper) RebuildRequest() error { if err := rw.rebuildSiteExt(); err != nil { return err } + if err := rw.rebuildDOOHExt(); err != nil { + return err + } if err := rw.rebuildSourceExt(); err != nil { return err } @@ -297,6 +312,25 @@ func (rw *RequestWrapper) rebuildAppExt() error { return nil } +func (rw *RequestWrapper) rebuildDOOHExt() error { + if rw.doohExt == nil || !rw.doohExt.Dirty() { + return nil + } + + doohJson, err := rw.doohExt.marshal() + if err != nil { + return err + } + + if doohJson != nil && rw.DOOH == nil { + rw.DOOH = &openrtb2.DOOH{Ext: doohJson} + } else if rw.DOOH != nil { + rw.DOOH.Ext = doohJson + } + + return nil +} + func (rw *RequestWrapper) rebuildRegExt() error { if rw.regExt == nil || !rw.regExt.Dirty() { return nil @@ -370,6 +404,7 @@ func (rw *RequestWrapper) Clone() *RequestWrapper { clone.appExt = rw.appExt.Clone() clone.regExt = rw.regExt.Clone() clone.siteExt = rw.siteExt.Clone() + clone.doohExt = rw.doohExt.Clone() clone.sourceExt = rw.sourceExt.Clone() return &clone @@ -1040,6 +1075,70 @@ func (ae *AppExt) Clone() *AppExt { return &clone } +// --------------------------------------------------------------- +// DOOHExt provides an interface for request.dooh.ext +// This is currently a placeholder for consistency with others - no useful attributes and getters/setters exist yet +// --------------------------------------------------------------- + +type DOOHExt struct { + ext map[string]json.RawMessage + extDirty bool +} + +func (de *DOOHExt) unmarshal(extJson json.RawMessage) error { + if len(de.ext) != 0 || de.Dirty() { + return nil + } + + de.ext = make(map[string]json.RawMessage) + + if len(extJson) == 0 { + return nil + } + + if err := json.Unmarshal(extJson, &de.ext); err != nil { + return err + } + + return nil +} + +func (de *DOOHExt) marshal() (json.RawMessage, error) { + de.extDirty = false + if len(de.ext) == 0 { + return nil, nil + } + return json.Marshal(de.ext) +} + +func (de *DOOHExt) Dirty() bool { + return de.extDirty +} + +func (de *DOOHExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range de.ext { + ext[k] = v + } + return ext +} + +func (de *DOOHExt) SetExt(ext map[string]json.RawMessage) { + de.ext = ext + de.extDirty = true +} + +func (de *DOOHExt) Clone() *DOOHExt { + if de == nil { + return nil + } + + clone := *de + clone.ext = maputil.Clone(de.ext) + + return &clone +} + // --------------------------------------------------------------- // RegExt provides an interface for request.regs.ext // --------------------------------------------------------------- diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go index e02dd741519..afa047e6909 100644 --- a/openrtb_ext/request_wrapper_test.go +++ b/openrtb_ext/request_wrapper_test.go @@ -41,6 +41,7 @@ func TestCloneRequestWrapper(t *testing.T) { appExt: &AppExt{prebidDirty: true}, regExt: &RegExt{usPrivacy: "foo"}, siteExt: &SiteExt{amp: ptrutil.ToPtr[int8](1)}, + doohExt: &DOOHExt{}, sourceExt: &SourceExt{schainDirty: true}, }, reqWrapCopy: &RequestWrapper{ @@ -60,6 +61,7 @@ func TestCloneRequestWrapper(t *testing.T) { appExt: &AppExt{prebidDirty: true}, regExt: &RegExt{usPrivacy: "foo"}, siteExt: &SiteExt{amp: ptrutil.ToPtr[int8](1)}, + doohExt: &DOOHExt{}, sourceExt: &SourceExt{schainDirty: true}, }, mutator: func(t *testing.T, reqWrap *RequestWrapper) {}, @@ -83,6 +85,7 @@ func TestCloneRequestWrapper(t *testing.T) { appExt: &AppExt{prebidDirty: true}, regExt: &RegExt{usPrivacy: "foo"}, siteExt: &SiteExt{amp: ptrutil.ToPtr[int8](1)}, + doohExt: &DOOHExt{}, sourceExt: &SourceExt{schainDirty: true}, }, reqWrapCopy: &RequestWrapper{ @@ -102,6 +105,7 @@ func TestCloneRequestWrapper(t *testing.T) { appExt: &AppExt{prebidDirty: true}, regExt: &RegExt{usPrivacy: "foo"}, siteExt: &SiteExt{amp: ptrutil.ToPtr[int8](1)}, + doohExt: &DOOHExt{}, sourceExt: &SourceExt{schainDirty: true}, }, mutator: func(t *testing.T, reqWrap *RequestWrapper) { @@ -115,6 +119,7 @@ func TestCloneRequestWrapper(t *testing.T) { reqWrap.appExt = nil reqWrap.regExt = nil reqWrap.siteExt = nil + reqWrap.doohExt = nil reqWrap.sourceExt = nil }, }, @@ -1206,6 +1211,141 @@ func TestCloneAppExt(t *testing.T) { } } +func TestRebuildDOOHExt(t *testing.T) { + // These permutations look a bit wonky + // Since DOOHExt currently exists for consistency but there isn't a single field + // expected - hence unable to test dirty and variations + // Once one is established, updated the permutations below similar to TestRebuildAppExt example + testCases := []struct { + description string + request openrtb2.BidRequest + requestDOOHExtWrapper DOOHExt + expectedRequest openrtb2.BidRequest + }{ + { + description: "Nil - Not Dirty", + request: openrtb2.BidRequest{}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Nil - Dirty", + request: openrtb2.BidRequest{}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: nil}, + }, + { + description: "Nil - Dirty - No Change", + request: openrtb2.BidRequest{}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Empty - Not Dirty", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{}}, + }, + { + description: "Empty - Dirty", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{}}, + }, + { + description: "Empty - Dirty - No Change", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{}}, + }, + { + description: "Populated - Not Dirty", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + }, + { + description: "Populated - Dirty", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + }, + { + description: "Populated - Dirty - No Change", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + }, + { + description: "Populated - Dirty - Cleared", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + }, + } + + for _, test := range testCases { + // create required filed in the test loop to keep test declaration easier to read + test.requestDOOHExtWrapper.ext = make(map[string]json.RawMessage) + + w := RequestWrapper{BidRequest: &test.request, doohExt: &test.requestDOOHExtWrapper} + w.RebuildRequest() + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestCloneDOOHExt(t *testing.T) { + testCases := []struct { + name string + DOOHExt *DOOHExt + DOOHExtCopy *DOOHExt // manual copy of above ext object to verify against + mutator func(t *testing.T, DOOHExt *DOOHExt) // function to modify the Ext object + }{ + { + name: "Nil", // Verify the nil case + DOOHExt: nil, + DOOHExtCopy: nil, + mutator: func(t *testing.T, DOOHExt *DOOHExt) {}, + }, + { + name: "NoMutate", + DOOHExt: &DOOHExt{ + ext: map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)}, + extDirty: true, + }, + DOOHExtCopy: &DOOHExt{ + ext: map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)}, + extDirty: true, + }, + mutator: func(t *testing.T, DOOHExt *DOOHExt) {}, + }, + { + name: "General", + DOOHExt: &DOOHExt{ + ext: map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)}, + extDirty: true, + }, + DOOHExtCopy: &DOOHExt{ + ext: map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)}, + extDirty: true, + }, + mutator: func(t *testing.T, DOOHExt *DOOHExt) { + DOOHExt.ext["A"] = json.RawMessage(`"string"`) + DOOHExt.ext["C"] = json.RawMessage(`{}`) + DOOHExt.extDirty = false + }, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + clone := test.DOOHExt.Clone() + test.mutator(t, test.DOOHExt) + assert.Equal(t, test.DOOHExtCopy, clone) + }) + } +} + func TestCloneRegExt(t *testing.T) { testCases := []struct { name string From db871196800b91de615cb101c8af9fe385b99f9a Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Thu, 21 Sep 2023 00:03:29 +0530 Subject: [PATCH 002/138] Record tmax timeout error in adapter error metric (#3104) --- errortypes/code.go | 1 + errortypes/errortypes.go | 19 +++++++++++++++++++ exchange/bidder.go | 4 +--- exchange/bidder_test.go | 4 ++-- exchange/exchange.go | 2 ++ metrics/metrics.go | 1 + 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/errortypes/code.go b/errortypes/code.go index c68eb19607a..ef8287f2d7d 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -14,6 +14,7 @@ const ( NoConversionRateErrorCode MalformedAcctErrorCode ModuleRejectionErrorCode + TmaxTimeoutErrorCode ) // Defines numeric codes for well-known warnings. diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index aff0482280e..01a5cca4af7 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -20,6 +20,25 @@ func (err *Timeout) Severity() Severity { return SeverityFatal } +// TmaxTimeout should be used to flag that remaining tmax duration is not enough to get response from bidder +// +// TmaxTimeout will not be written to the app log, since it's not an actionable item for the Prebid Server hosts. +type TmaxTimeout struct { + Message string +} + +func (err *TmaxTimeout) Error() string { + return err.Message +} + +func (err *TmaxTimeout) Code() int { + return TmaxTimeoutErrorCode +} + +func (err *TmaxTimeout) Severity() Severity { + return SeverityFatal +} + // BadInput should be used when returning errors which are caused by bad input. // It should _not_ be used if the error is a server-side issue (e.g. failed to send the external request). // diff --git a/exchange/bidder.go b/exchange/bidder.go index 838c46f73d1..dfdb7cdf85f 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -88,8 +88,6 @@ const ( Gzip string = "GZIP" ) -var errTmaxTimeout = errors.New("exceeded tmax duration") - // AdaptBidder converts an adapters.Bidder into an exchange.AdaptedBidder. // // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter" @@ -551,7 +549,7 @@ func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.Re bidder.me.RecordTMaxTimeout() return &httpCallInfo{ request: req, - err: errTmaxTimeout, + err: &errortypes.TmaxTimeout{Message: "exceeded tmax duration"}, } } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index e2d476c0411..65f6b795247 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -3221,7 +3221,7 @@ func TestDoRequestImplWithTmax(t *testing.T) { ctxDeadline: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), description: "returns-tmax-timeout-error", tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, PBSResponsePreparationDuration: 100, BidderNetworkLatencyBuffer: 10, BidderResponseDurationMin: 5000}, - assertFn: func(err error) { assert.Equal(t, errTmaxTimeout, err) }, + assertFn: func(err error) { assert.Equal(t, &errortypes.TmaxTimeout{Message: "exceeded tmax duration"}, err) }, }, { ctxDeadline: time.Now().Add(5 * time.Second), @@ -3296,7 +3296,7 @@ func TestDoRequestImplWithTmaxTimeout(t *testing.T) { ctxDeadline: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), description: "returns-tmax-timeout-error", tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, PBSResponsePreparationDuration: 100, BidderNetworkLatencyBuffer: 10, BidderResponseDurationMin: 5000}, - assertFn: func(err error) { assert.Equal(t, errTmaxTimeout, err) }, + assertFn: func(err error) { assert.Equal(t, &errortypes.TmaxTimeout{Message: "exceeded tmax duration"}, err) }, }, } for _, test := range tests { diff --git a/exchange/exchange.go b/exchange/exchange.go index 136f1040740..7544b4a7020 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -844,6 +844,8 @@ func errorsToMetric(errs []error) map[metrics.AdapterError]struct{} { ret[metrics.AdapterErrorFailedToRequestBids] = s case errortypes.AlternateBidderCodeWarningCode: ret[metrics.AdapterErrorValidation] = s + case errortypes.TmaxTimeoutErrorCode: + ret[metrics.AdapterErrorTmaxTimeout] = s default: ret[metrics.AdapterErrorUnknown] = s } diff --git a/metrics/metrics.go b/metrics/metrics.go index d39c06d7869..a73019bed5e 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -271,6 +271,7 @@ const ( AdapterErrorTimeout AdapterError = "timeout" AdapterErrorFailedToRequestBids AdapterError = "failedtorequestbid" AdapterErrorValidation AdapterError = "validation" + AdapterErrorTmaxTimeout AdapterError = "tmaxtimeout" AdapterErrorUnknown AdapterError = "unknown_error" ) From 55302c6d9e4f8806f2044560d301f0043da08873 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 20 Sep 2023 17:35:54 -0400 Subject: [PATCH 003/138] Cleanup Prometheus Type Conversions (#3106) --- metrics/prometheus/preload.go | 30 ++++--- metrics/prometheus/type_conversion.go | 123 +------------------------- 2 files changed, 17 insertions(+), 136 deletions(-) diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go index dae7c14dc5b..59f70cfb9fb 100644 --- a/metrics/prometheus/preload.go +++ b/metrics/prometheus/preload.go @@ -2,28 +2,30 @@ package prometheusmetrics import ( "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/prometheus/client_golang/prometheus" ) func preloadLabelValues(m *Metrics, syncerKeys []string, moduleStageNames map[string][]string) { var ( - setUidStatusValues = setUidStatusesAsString() - adapterErrorValues = adapterErrorsAsString() - adapterValues = adaptersAsString() + adapterErrorValues = enumAsString(metrics.AdapterErrors()) + adapterValues = enumAsString(openrtb_ext.CoreBidderNames()) bidTypeValues = []string{markupDeliveryAdm, markupDeliveryNurl} boolValues = boolValuesAsString() - cacheResultValues = cacheResultsAsString() + cacheResultValues = enumAsString(metrics.CacheResults()) connectionErrorValues = []string{connectionAcceptError, connectionCloseError} - cookieValues = cookieTypesAsString() - cookieSyncStatusValues = cookieSyncStatusesAsString() - overheadTypes = overheadTypesAsString() - requestTypeValues = requestTypesAsString() - requestStatusValues = requestStatusesAsString() - storedDataFetchTypeValues = storedDataFetchTypesAsString() - storedDataErrorValues = storedDataErrorsAsString() - syncerRequestStatusValues = syncerRequestStatusesAsString() - syncerSetsStatusValues = syncerSetStatusesAsString() + cookieSyncStatusValues = enumAsString(metrics.CookieSyncStatuses()) + cookieValues = enumAsString(metrics.CookieTypes()) + overheadTypes = enumAsString(metrics.OverheadTypes()) + requestStatusValues = enumAsString(metrics.RequestStatuses()) + requestTypeValues = enumAsString(metrics.RequestTypes()) + setUidStatusValues = enumAsString(metrics.SetUidStatuses()) sourceValues = []string{sourceRequest} + storedDataErrorValues = enumAsString(metrics.StoredDataErrors()) + storedDataFetchTypeValues = enumAsString(metrics.StoredDataFetchTypes()) + syncerRequestStatusValues = enumAsString(metrics.SyncerRequestStatuses()) + syncerSetsStatusValues = enumAsString(metrics.SyncerSetUidStatuses()) + tcfVersionValues = enumAsString(metrics.TCFVersions()) ) preloadLabelValuesForCounter(m.connectionsError, map[string][]string{ @@ -224,7 +226,7 @@ func preloadLabelValues(m *Metrics, syncerKeys []string, moduleStageNames map[st preloadLabelValuesForCounter(m.privacyTCF, map[string][]string{ sourceLabel: sourceValues, - versionLabel: tcfVersionsAsString(), + versionLabel: tcfVersionValues, }) if !m.metricsDisabled.AdapterGDPRRequestBlocked { diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go index 07e4e89c43d..0ae5366b3f7 100644 --- a/metrics/prometheus/type_conversion.go +++ b/metrics/prometheus/type_conversion.go @@ -2,22 +2,9 @@ package prometheusmetrics import ( "strconv" - - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" ) -func adaptersAsString() []string { - values := openrtb_ext.CoreBidderNames() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func adapterErrorsAsString() []string { - values := metrics.AdapterErrors() +func enumAsString[T ~string](values []T) []string { valuesAsString := make([]string, len(values)) for i, v := range values { valuesAsString[i] = string(v) @@ -31,111 +18,3 @@ func boolValuesAsString() []string { strconv.FormatBool(false), } } - -func cacheResultsAsString() []string { - values := metrics.CacheResults() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func cookieTypesAsString() []string { - values := metrics.CookieTypes() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func cookieSyncStatusesAsString() []string { - values := metrics.CookieSyncStatuses() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func requestStatusesAsString() []string { - values := metrics.RequestStatuses() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func syncerRequestStatusesAsString() []string { - values := metrics.SyncerRequestStatuses() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func overheadTypesAsString() []string { - overheadTypes := metrics.OverheadTypes() - overheadTypesAsString := make([]string, len(overheadTypes)) - for i, ot := range overheadTypes { - overheadTypesAsString[i] = ot.String() - } - return overheadTypesAsString -} - -func syncerSetStatusesAsString() []string { - values := metrics.SyncerSetUidStatuses() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func requestTypesAsString() []string { - values := metrics.RequestTypes() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func setUidStatusesAsString() []string { - values := metrics.SetUidStatuses() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func storedDataFetchTypesAsString() []string { - values := metrics.StoredDataFetchTypes() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func storedDataErrorsAsString() []string { - values := metrics.StoredDataErrors() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func tcfVersionsAsString() []string { - values := metrics.TCFVersions() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} From b9c2614798d16dbb27841dc6a1c6103566a04b49 Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Mon, 25 Sep 2023 22:22:54 +0530 Subject: [PATCH 004/138] make case insensitive comparison in auction request (#3113) --- endpoints/openrtb2/auction.go | 21 +-- endpoints/openrtb2/auction_test.go | 78 ++++++++-- .../exemplary/all-ext-case-insensitive.json | 140 ++++++++++++++++++ .../valid-whole/exemplary/all-ext.json | 86 ++++++----- .../exemplary/simple-case-insensitive.json | 61 ++++++++ .../valid-whole/exemplary/simple.json | 40 ++--- exchange/exchange_test.go | 6 +- exchange/utils.go | 3 +- 8 files changed, 355 insertions(+), 80 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple-case-insensitive.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 375756cc565..f419ca341ee 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1520,12 +1520,12 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb_ext.ImpWrapper, aliases ma errL := []error{} for bidder, ext := range prebid.Bidder { - coreBidder := bidder + coreBidder, _ := openrtb_ext.NormalizeBidderName(bidder) if tmp, isAlias := aliases[bidder]; isAlias { - coreBidder = tmp + coreBidder = openrtb_ext.BidderName(tmp) } - if coreBidderNormalized, isValid := deps.bidderMap[coreBidder]; isValid { + if coreBidderNormalized, isValid := deps.bidderMap[coreBidder.String()]; isValid { if err := deps.paramsValidator.Validate(coreBidderNormalized, ext); err != nil { return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder.%s failed validation.\n%v", impIndex, bidder, err)} } @@ -1585,18 +1585,21 @@ func (deps *endpointDeps) parseBidExt(req *openrtb_ext.RequestWrapper) error { } func (deps *endpointDeps) validateAliases(aliases map[string]string) error { - for alias, coreBidder := range aliases { - if _, isCoreBidderDisabled := deps.disabledBidders[coreBidder]; isCoreBidderDisabled { - return fmt.Errorf("request.ext.prebid.aliases.%s refers to disabled bidder: %s", alias, coreBidder) + for alias, bidderName := range aliases { + normalisedBidderName, _ := openrtb_ext.NormalizeBidderName(bidderName) + coreBidderName := normalisedBidderName.String() + if _, isCoreBidderDisabled := deps.disabledBidders[coreBidderName]; isCoreBidderDisabled { + return fmt.Errorf("request.ext.prebid.aliases.%s refers to disabled bidder: %s", alias, bidderName) } - if _, isCoreBidder := deps.bidderMap[coreBidder]; !isCoreBidder { - return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", alias, coreBidder) + if _, isCoreBidder := deps.bidderMap[coreBidderName]; !isCoreBidder { + return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", alias, bidderName) } - if alias == coreBidder { + if alias == coreBidderName { return fmt.Errorf("request.ext.prebid.aliases.%s defines a no-op alias. Choose a different alias, or remove this entry.", alias) } + aliases[alias] = coreBidderName } return nil } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index f60fd98e926..08637df2493 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -2968,19 +2968,21 @@ func TestValidateImpExt(t *testing.T) { for _, group := range testGroups { for _, test := range group.testCases { - imp := &openrtb2.Imp{Ext: test.impExt} - impWrapper := &openrtb_ext.ImpWrapper{Imp: imp} + t.Run(test.description, func(t *testing.T) { + imp := &openrtb2.Imp{Ext: test.impExt} + impWrapper := &openrtb_ext.ImpWrapper{Imp: imp} - errs := deps.validateImpExt(impWrapper, nil, 0, false, nil) + errs := deps.validateImpExt(impWrapper, nil, 0, false, nil) - assert.NoError(t, impWrapper.RebuildImp(), test.description+":rebuild_imp") + assert.NoError(t, impWrapper.RebuildImp(), test.description+":rebuild_imp") - if len(test.expectedImpExt) > 0 { - assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description) - } else { - assert.Empty(t, imp.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(imp.Ext), group.description, test.description) - } - assert.Equal(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) + if len(test.expectedImpExt) > 0 { + assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description) + } else { + assert.Empty(t, imp.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(imp.Ext), group.description, test.description) + } + assert.Equal(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) + }) } } } @@ -5978,3 +5980,59 @@ func TestSetSeatNonBidRaw(t *testing.T) { }) } } + +func TestValidateAliases(t *testing.T) { + deps := &endpointDeps{ + disabledBidders: map[string]string{"rubicon": "rubicon"}, + bidderMap: map[string]openrtb_ext.BidderName{"appnexus": openrtb_ext.BidderName("appnexus")}, + } + + testCases := []struct { + description string + aliases map[string]string + expectedAliases map[string]string + expectedError error + }{ + { + description: "valid case", + aliases: map[string]string{"test": "appnexus"}, + expectedAliases: map[string]string{"test": "appnexus"}, + expectedError: nil, + }, + { + description: "valid case - case insensitive", + aliases: map[string]string{"test": "Appnexus"}, + expectedAliases: map[string]string{"test": "appnexus"}, + expectedError: nil, + }, + { + description: "disabled bidder", + aliases: map[string]string{"test": "rubicon"}, + expectedAliases: nil, + expectedError: errors.New("request.ext.prebid.aliases.test refers to disabled bidder: rubicon"), + }, + { + description: "coreBidderName not found", + aliases: map[string]string{"test": "anyBidder"}, + expectedAliases: nil, + expectedError: errors.New("request.ext.prebid.aliases.test refers to unknown bidder: anyBidder"), + }, + { + description: "alias name is coreBidder name", + aliases: map[string]string{"appnexus": "appnexus"}, + expectedAliases: nil, + expectedError: errors.New("request.ext.prebid.aliases.appnexus defines a no-op alias. Choose a different alias, or remove this entry."), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + err := deps.validateAliases(testCase.aliases) + if err != nil { + assert.Equal(t, testCase.expectedError, err) + } else { + assert.ObjectsAreEqualValues(testCase.expectedAliases, map[string]string{"test": "appnexus"}) + } + }) + } +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json new file mode 100644 index 00000000000..20997076af2 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json @@ -0,0 +1,140 @@ +{ + "description": "This demonstrates all extension in case insensitive.", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "ext": { + "consent": "gdpr-consent-string", + "prebid": { + "buyeruids": { + "appnexus": "override-appnexus-id-in-cookie" + } + } + } + }, + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1NYN" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "districtm": { + "placementId": 105 + }, + "rubicon": { + "accountId": 1001, + "siteId": 113932, + "zoneId": 535510 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "aliases": { + "districtm": "Appnexus" + }, + "bidadjustmentfactors": { + "appnexus": 1.01, + "districtm": 0.98, + "rubicon": 0.99 + }, + "cache": { + "bids": {} + }, + "channel": { + "name": "video", + "version": "1.0" + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1.01 + } + ], + "seat": "appnexus" + }, + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0.98 + } + ], + "seat": "districtm" + }, + { + "bid": [ + { + "id": "rubicon-bid", + "impid": "some-impression-id", + "price": 0.99 + } + ], + "seat": "rubicon" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json index 8f64fd2a5fd..02dc6160d49 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json @@ -2,8 +2,16 @@ "description": "This demonstrates all of the OpenRTB extensions supported by Prebid Server. Very few requests will need all of these at once.", "config": { "mockBidders": [ - {"bidderName": "appnexus", "currency": "USD", "price": 1.00}, - {"bidderName": "rubicon", "currency": "USD", "price": 1.00} + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } ] }, "mockBidRequest": { @@ -91,42 +99,42 @@ } }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 1.01 - } - ], - "seat": "appnexus" - }, - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 0.98 - } - ], - "seat": "districtm" - }, - { - "bid": [ - { - "id": "rubicon-bid", - "impid": "some-impression-id", - "price": 0.99 - } - ], - "seat": "rubicon" - } - ], - "bidid":"test bid id", - "cur":"USD", - "nbr":0 + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1.01 + } + ], + "seat": "appnexus" + }, + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0.98 + } + ], + "seat": "districtm" + }, + { + "bid": [ + { + "id": "rubicon-bid", + "impid": "some-impression-id", + "price": 0.99 + } + ], + "seat": "rubicon" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple-case-insensitive.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple-case-insensitive.json new file mode 100644 index 00000000000..9b66a59d0a5 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple-case-insensitive.json @@ -0,0 +1,61 @@ +{ + "description": "Simple request - bidder case insensitive", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 0.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "Appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": {} + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0 + } + ], + "seat": "Appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json index ba9079d4675..5a7dbd20747 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json @@ -2,7 +2,11 @@ "description": "Simple request", "config": { "mockBidders": [ - {"bidderName": "appnexus", "currency": "USD", "price": 0.00} + { + "bidderName": "appnexus", + "currency": "USD", + "price": 0.00 + } ] }, "mockBidRequest": { @@ -36,22 +40,22 @@ "ext": {} }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 0 - } - ], - "seat": "appnexus" - } - ], - "bidid":"test bid id", - "cur":"USD", - "nbr":0 + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0 + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 8ca96880781..c4b85ebeb1f 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -769,7 +769,7 @@ func TestAdapterCurrency(t *testing.T) { categoriesFetcher: nilCategoryFetcher{}, bidIDGenerator: &mockBidIDGenerator{false, false}, adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ - openrtb_ext.BidderName("foo"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil, ""), + openrtb_ext.BidderName("appnexus"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("appnexus"), nil, ""), }, } e.requestSplitter = requestSplitter{ @@ -783,7 +783,7 @@ func TestAdapterCurrency(t *testing.T) { Imp: []openrtb2.Imp{{ ID: "some-impression-id", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, - Ext: json.RawMessage(`{"prebid":{"bidder":{"foo":{"placementId":1}}}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":1}}}}`), }}, Site: &openrtb2.Site{ Page: "prebid.org", @@ -805,7 +805,7 @@ func TestAdapterCurrency(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "some-request-id", response.ID, "Response ID") assert.Empty(t, response.SeatBid, "Response Bids") - assert.Contains(t, string(response.Ext), `"errors":{"foo":[{"code":5,"message":"The adapter failed to generate any bid requests, but also failed to generate an error explaining why"}]}`, "Response Ext") + assert.Contains(t, string(response.Ext), `"errors":{"appnexus":[{"code":5,"message":"The adapter failed to generate any bid requests, but also failed to generate an error explaining why"}]}`, "Response Ext") // Test Currency Converter Properly Passed To Adapter if assert.NotNil(t, mockBidder.lastExtraRequestInfo, "Currency Conversion Argument") { diff --git a/exchange/utils.go b/exchange/utils.go index 6c47bcaa9c0..0a38e487632 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -762,10 +762,11 @@ func setUserExtWithCopy(request *openrtb2.BidRequest, userExtJSON json.RawMessag // resolveBidder returns the known BidderName associated with bidder, if bidder is an alias. If it's not an alias, the bidder is returned. func resolveBidder(bidder string, aliases map[string]string) openrtb_ext.BidderName { + normalisedBidderName, _ := openrtb_ext.NormalizeBidderName(bidder) if coreBidder, ok := aliases[bidder]; ok { return openrtb_ext.BidderName(coreBidder) } - return openrtb_ext.BidderName(bidder) + return normalisedBidderName } // parseAliases parses the aliases from the BidRequest From a91e40c8429a224a186cb998f99e35e97432d43d Mon Sep 17 00:00:00 2001 From: freemmy Date: Tue, 26 Sep 2023 12:51:19 +0700 Subject: [PATCH 005/138] Silvermob: host validation (us, eu, apac) (#3110) --- adapters/silvermob/silvermob.go | 14 ++++- .../supplemental/invalid-host.json | 53 +++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 adapters/silvermob/silvermobtest/supplemental/invalid-host.json diff --git a/adapters/silvermob/silvermob.go b/adapters/silvermob/silvermob.go index 9130ab4d96a..d4a1b55b45f 100644 --- a/adapters/silvermob/silvermob.go +++ b/adapters/silvermob/silvermob.go @@ -18,6 +18,10 @@ type SilverMobAdapter struct { endpoint *template.Template } +func isValidHost(host string) bool { + return host == "eu" || host == "us" || host == "apac" +} + // Builder builds a new instance of the SilverMob adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { template, err := template.New("endpointTemplate").Parse(config.Endpoint) @@ -121,8 +125,14 @@ func (a *SilverMobAdapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.Ext } func (a *SilverMobAdapter) buildEndpointURL(params *openrtb_ext.ExtSilverMob) (string, error) { - endpointParams := macros.EndpointTemplateParams{ZoneID: params.ZoneID, Host: params.Host} - return macros.ResolveMacros(a.endpoint, endpointParams) + if isValidHost(params.Host) { + endpointParams := macros.EndpointTemplateParams{ZoneID: params.ZoneID, Host: params.Host} + return macros.ResolveMacros(a.endpoint, endpointParams) + } else { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("invalid host %s", params.Host), + } + } } func (a *SilverMobAdapter) MakeBids( diff --git a/adapters/silvermob/silvermobtest/supplemental/invalid-host.json b/adapters/silvermob/silvermobtest/supplemental/invalid-host.json new file mode 100644 index 00000000000..f8b2cd3442f --- /dev/null +++ b/adapters/silvermob/silvermobtest/supplemental/invalid-host.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "host": "some_host", + "zoneid": "0" + } + } + } + ] + }, + "httpCalls": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "invalid host some_host", + "comparison": "literal" + } + ] + } \ No newline at end of file From 63170b8484c6a1eb35194d0d04b22c5b76126c9f Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Tue, 26 Sep 2023 11:26:52 +0530 Subject: [PATCH 006/138] make bidderInfo endpoint case insensitive (#3136) co-authored by @gargcreation1992 --- endpoints/info/bidders_detail.go | 20 ++++++++- endpoints/info/bidders_detail_test.go | 62 +++++++++++++++++---------- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/endpoints/info/bidders_detail.go b/endpoints/info/bidders_detail.go index 9b8d42686ae..d9dc776f50d 100644 --- a/endpoints/info/bidders_detail.go +++ b/endpoints/info/bidders_detail.go @@ -27,7 +27,11 @@ func NewBiddersDetailEndpoint(bidders config.BidderInfos, aliases map[string]str return func(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) { bidder := ps.ByName("bidderName") - if response, ok := responses[bidder]; ok { + coreBidderName, found := getNormalisedBidderName(bidder, aliases) + if !found { + w.WriteHeader(http.StatusNotFound) + } + if response, ok := responses[coreBidderName]; ok { w.Header().Set("Content-Type", "application/json") if _, err := w.Write(response); err != nil { glog.Errorf("error writing response to /info/bidders/%s: %v", bidder, err) @@ -38,6 +42,20 @@ func NewBiddersDetailEndpoint(bidders config.BidderInfos, aliases map[string]str } } +func getNormalisedBidderName(bidderName string, aliases map[string]string) (string, bool) { + if strings.ToLower(bidderName) == "all" { + return "all", true + } + coreBidderName, ok := openrtb_ext.NormalizeBidderName(bidderName) + if !ok { //check default aliases if not found in coreBidders + if _, isDefaultAlias := aliases[bidderName]; isDefaultAlias { + return bidderName, true + } + return "", false + } + return coreBidderName.String(), true +} + func prepareBiddersDetailResponse(bidders config.BidderInfos, aliases map[string]string) (map[string][]byte, error) { details, err := mapDetails(bidders, aliases) if err != nil { diff --git a/endpoints/info/bidders_detail_test.go b/endpoints/info/bidders_detail_test.go index 3be24acfdba..435d0cec92c 100644 --- a/endpoints/info/bidders_detail_test.go +++ b/endpoints/info/bidders_detail_test.go @@ -2,6 +2,7 @@ package info import ( "bytes" + "fmt" "io" "net/http" "net/http/httptest" @@ -365,22 +366,22 @@ func TestMapMediaTypes(t *testing.T) { func TestBiddersDetailHandler(t *testing.T) { bidderAInfo := config.BidderInfo{Endpoint: "https://secureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} bidderAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"}}`) - aliasAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"},"aliasOf":"a"}`) + aliasAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"},"aliasOf":"appnexus"}`) bidderBInfo := config.BidderInfo{Endpoint: "http://unsecureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} bidderBResponse := []byte(`{"status":"ACTIVE","usesHttps":false,"maintainer":{"email":"bidderB"}}`) allResponse := bytes.Buffer{} - allResponse.WriteString(`{"a":`) - allResponse.Write(bidderAResponse) - allResponse.WriteString(`,"aAlias":`) + allResponse.WriteString(`{"aAlias":`) allResponse.Write(aliasAResponse) - allResponse.WriteString(`,"b":`) + allResponse.WriteString(`,"appnexus":`) + allResponse.Write(bidderAResponse) + allResponse.WriteString(`,"rubicon":`) allResponse.Write(bidderBResponse) allResponse.WriteString(`}`) - bidders := config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo} - aliases := map[string]string{"aAlias": "a"} + bidders := config.BidderInfos{"appnexus": bidderAInfo, "rubicon": bidderBInfo} + aliases := map[string]string{"aAlias": "appnexus"} handler := NewBiddersDetailEndpoint(bidders, aliases) @@ -393,14 +394,21 @@ func TestBiddersDetailHandler(t *testing.T) { }{ { description: "Bidder A", - givenBidder: "a", + givenBidder: "appnexus", expectedStatus: http.StatusOK, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, expectedResponse: bidderAResponse, }, { description: "Bidder B", - givenBidder: "b", + givenBidder: "rubicon", + expectedStatus: http.StatusOK, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + expectedResponse: bidderBResponse, + }, + { + description: "Bidder B - case insensitive", + givenBidder: "RUBICON", expectedStatus: http.StatusOK, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, expectedResponse: bidderBResponse, @@ -412,6 +420,13 @@ func TestBiddersDetailHandler(t *testing.T) { expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, expectedResponse: aliasAResponse, }, + { + description: "Bidder A Alias - case insensitive", + givenBidder: "aAlias", + expectedStatus: http.StatusOK, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + expectedResponse: aliasAResponse, + }, { description: "All Bidders", givenBidder: "all", @@ -420,11 +435,11 @@ func TestBiddersDetailHandler(t *testing.T) { expectedResponse: allResponse.Bytes(), }, { - description: "All Bidders - Wrong Case", - givenBidder: "ALL", - expectedStatus: http.StatusNotFound, - expectedHeaders: http.Header{}, - expectedResponse: []byte{}, + description: "All Bidders - Case insensitive", + givenBidder: "All", + expectedStatus: http.StatusOK, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + expectedResponse: allResponse.Bytes(), }, { description: "Invalid Bidder", @@ -436,16 +451,19 @@ func TestBiddersDetailHandler(t *testing.T) { } for _, test := range testCases { - responseRecorder := httptest.NewRecorder() - handler(responseRecorder, nil, httprouter.Params{{"bidderName", test.givenBidder}}) + t.Run(test.description, func(t *testing.T) { + responseRecorder := httptest.NewRecorder() + handler(responseRecorder, nil, httprouter.Params{{"bidderName", test.givenBidder}}) - result := responseRecorder.Result() - assert.Equal(t, result.StatusCode, test.expectedStatus, test.description+":statuscode") + result := responseRecorder.Result() + assert.Equal(t, result.StatusCode, test.expectedStatus, test.description+":statuscode") - resultBody, _ := io.ReadAll(result.Body) - assert.Equal(t, test.expectedResponse, resultBody, test.description+":body") + resultBody, _ := io.ReadAll(result.Body) + fmt.Println(string(test.expectedResponse)) + assert.Equal(t, test.expectedResponse, resultBody, test.description+":body") - resultHeaders := result.Header - assert.Equal(t, test.expectedHeaders, resultHeaders, test.description+":headers") + resultHeaders := result.Header + assert.Equal(t, test.expectedHeaders, resultHeaders, test.description+":headers") + }) } } From bc81af56826c90d262a1848f7a503daff4fe4a24 Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Tue, 26 Sep 2023 11:28:47 +0530 Subject: [PATCH 007/138] extract directory name only if file is not removed and file is in adapters directory (#3145) co-authored by @onkarvhanumante --- .github/workflows/adapter-code-coverage.yml | 14 +++++++------- .github/workflows/helpers/pull-request-utils.js | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/adapter-code-coverage.yml b/.github/workflows/adapter-code-coverage.yml index 5b5dc9331e5..b4c3f0745d6 100644 --- a/.github/workflows/adapter-code-coverage.yml +++ b/.github/workflows/adapter-code-coverage.yml @@ -28,9 +28,9 @@ jobs: result-encoding: string script: | const utils = require('./.github/workflows/helpers/pull-request-utils.js') - function directoryExtractor(filepath) { - // extract directory name from filepath of the form adapters//*.go - if (filepath.startsWith("adapters/") && filepath.split("/").length > 2) { + function directoryExtractor(filepath, status) { + // extract directory name only if file is not removed and file is in adapters directory + if (status != "removed" && filepath.startsWith("adapters/") && filepath.split("/").length > 2) { return filepath.split("/")[1] } return "" @@ -41,7 +41,7 @@ jobs: - name: Run coverage tests id: run_coverage - if: ${{ steps.get_directories.outputs.result }} != "" + if: steps.get_directories.outputs.result != '' run: | directories=$(echo '${{ steps.get_directories.outputs.result }}' | jq -r '.[]') go mod download @@ -74,7 +74,7 @@ jobs: repository: prebid/prebid-server - name: Commit coverage files to coverage-preview branch - if: ${{ steps.run_coverage.outputs.coverage_dir }} != "" + if: steps.run_coverage.outputs.coverage_dir != '' id: commit_coverage run: | directory=.github/preview/${{ github.run_id }}_$(date +%s) @@ -88,11 +88,11 @@ jobs: echo "remote_coverage_preview_dir=${directory}" >> $GITHUB_OUTPUT - name: Checkout master branch - if: ${{ steps.get_directories.outputs.result }} != "" + if: steps.get_directories.outputs.result != '' run: git checkout master - name: Add coverage summary to pull request - if: ${{ steps.run_coverage.outputs.coverage_dir }} != "" && ${{ steps.commit_coverage.outputs.remote_coverage_preview_dir }} != "" + if: steps.run_coverage.outputs.coverage_dir != '' && steps.commit_coverage.outputs.remote_coverage_preview_dir != '' uses: actions/github-script@v6 with: script: | diff --git a/.github/workflows/helpers/pull-request-utils.js b/.github/workflows/helpers/pull-request-utils.js index 78ab0bb2bc5..941ae5b6953 100644 --- a/.github/workflows/helpers/pull-request-utils.js +++ b/.github/workflows/helpers/pull-request-utils.js @@ -213,8 +213,8 @@ class diffHelper { }) const directories = [] - for (const { filename } of data) { - const directory = directoryExtractor(filename) + for (const { filename, status } of data) { + const directory = directoryExtractor(filename, status) if (directory != "" && !directories.includes(directory)) { directories.push(directory) } From 00bdbef5368b68e3bcbe0a12f797e79e2ba80d32 Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Tue, 26 Sep 2023 11:33:23 +0530 Subject: [PATCH 008/138] Support case insensitive bidder name in ext.prebid.storedbidresponse.bidder (#3139) --- endpoints/openrtb2/auction.go | 7 +++- endpoints/openrtb2/auction_test.go | 13 ++++++- ...ored-bid-resp-insensitive-bidder-name.json | 39 +++++++++++++++++++ stored_responses/stored_responses.go | 7 +++- 4 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-insensitive-bidder-name.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index f419ca341ee..d8b5c41b018 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -2486,7 +2486,12 @@ func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage } for bidderName := range bidResponses { - if _, present := bidderExts[bidderName]; !present { + bidder := bidderName + normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidder) + if ok { + bidder = normalizedCoreBidder.String() + } + if _, present := bidderExts[bidder]; !present { return generateStoredBidResponseValidationError(impId) } } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 08637df2493..c071d6b0950 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -4954,9 +4954,11 @@ func TestParseRequestStoredResponses(t *testing.T) { func TestParseRequestStoredBidResponses(t *testing.T) { bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "testBidder1"}], "bidid": "123", "cur": "USD"}`) bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2"}], "seat": "testBidder2"}], "bidid": "124", "cur": "USD"}`) + bidRespId3 := json.RawMessage(`{"id": "resp_id3", "seatbid": [{"bid": [{"id": "bid_id3"}], "seat": "APPNEXUS"}], "bidid": "125", "cur": "USD"}`) mockStoredBidResponses := map[string]json.RawMessage{ "bidResponseId1": bidRespId1, "bidResponseId2": bidRespId2, + "bidResponseId3": bidRespId3, } tests := []struct { @@ -4974,6 +4976,14 @@ func TestParseRequestStoredBidResponses(t *testing.T) { }, expectedErrorCount: 0, }, + { + name: "req imp has valid stored bid response with case insensitive bidder name", + givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-insensitive-bidder-name.json"), + expectedStoredBidResponses: map[string]map[string]json.RawMessage{ + "imp-id3": {"APPNEXUS": bidRespId3}, + }, + expectedErrorCount: 0, + }, { name: "req has two imps with valid stored bid responses", givenRequestBody: validRequest(t, "req-two-imps-stored-bid-responses.json"), @@ -5014,7 +5024,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) { map[string]string{}, false, []byte{}, - map[string]openrtb_ext.BidderName{"testBidder1": "testBidder1", "testBidder2": "testBidder2"}, + map[string]openrtb_ext.BidderName{"testBidder1": "testBidder1", "testBidder2": "testBidder2", "appnexus": "appnexus"}, nil, nil, hardcodedResponseIPValidator{response: true}, @@ -5027,7 +5037,6 @@ func TestParseRequestStoredBidResponses(t *testing.T) { req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody)) _, _, _, storedBidResponses, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor) - if test.expectedErrorCount == 0 { assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses, "stored responses should match") } else { diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-insensitive-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-insensitive-bidder-name.json new file mode 100644 index 00000000000..e7b688d4d83 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-insensitive-bidder-name.json @@ -0,0 +1,39 @@ +{ + "description": "request with impression with stored bid response with sensitive bidder name", + "mockBidRequest": { + "id": "request-with-stored-resp", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id3", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "prebid": { + "storedbidresponse": [ + { + "bidder": "APPNEXUS", + "id": "bidResponseId3" + } + ] + } + } + } + ], + "user": { + "yob": 1989 + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/stored_responses/stored_responses.go b/stored_responses/stored_responses.go index 19b010fb12d..6a5da01deba 100644 --- a/stored_responses/stored_responses.go +++ b/stored_responses/stored_responses.go @@ -97,8 +97,13 @@ func extractStoredResponsesIds(impInfo []ImpExtPrebidData, if len(bidderResp.ID) == 0 || len(bidderResp.Bidder) == 0 { return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ", index) } + bidderName := bidderResp.Bidder + normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidderResp.Bidder) + if ok { + bidderName = normalizedCoreBidder.String() + } //check if bidder is valid/exists - if _, isValid := bidderMap[bidderResp.Bidder]; !isValid { + if _, isValid := bidderMap[bidderName]; !isValid { return nil, nil, nil, nil, fmt.Errorf("request.imp[impId: %s].ext.prebid.bidder contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impId, bidderResp.Bidder) } // bidder is unique per one bid stored response From 940355c3061527c362e1b742f67f00428c461c55 Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Tue, 26 Sep 2023 11:36:38 +0530 Subject: [PATCH 009/138] Support case insensitive bidder name in adjustment factors (#3140) --- endpoints/openrtb2/auction.go | 9 +- ...r-adjustment-factors-case-insensitive.json | 119 ++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidder-adjustment-factors-case-insensitive.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index d8b5c41b018..bede06217e4 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -964,7 +964,14 @@ func (deps *endpointDeps) validateBidAdjustmentFactors(adjustmentFactors map[str if adjustmentFactor <= 0 { return fmt.Errorf("request.ext.prebid.bidadjustmentfactors.%s must be a positive number. Got %f", bidderToAdjust, adjustmentFactor) } - if _, isBidder := deps.bidderMap[bidderToAdjust]; !isBidder { + + bidderName := bidderToAdjust + normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidderToAdjust) + if ok { + bidderName = normalizedCoreBidder.String() + } + + if _, isBidder := deps.bidderMap[bidderName]; !isBidder { if _, isAlias := aliases[bidderToAdjust]; !isAlias { return fmt.Errorf("request.ext.prebid.bidadjustmentfactors.%s is not a known bidder or alias", bidderToAdjust) } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidder-adjustment-factors-case-insensitive.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidder-adjustment-factors-case-insensitive.json new file mode 100644 index 00000000000..2fa4f37cc88 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidder-adjustment-factors-case-insensitive.json @@ -0,0 +1,119 @@ +{ + "description": "This demonstrates bid adjustment factors with case insensitive bidder names", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "ext": { + "consent": "gdpr-consent-string" + } + }, + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1NYN" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "APPNEXUS": { + "placementId": 12883451 + }, + "districtm": { + "placementId": 105 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "aliases": { + "districtm": "APPNEXUS" + }, + "bidadjustmentfactors": { + "APPNEXUS": 1.01, + "districtm": 0.98 + }, + "cache": { + "bids": {} + }, + "channel": { + "name": "video", + "version": "1.0" + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1.01 + } + ], + "seat": "APPNEXUS" + }, + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0.98 + } + ], + "seat": "districtm" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file From 27498c8584b5c96d86a10b8873b835a2009a4ac0 Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Tue, 26 Sep 2023 11:37:14 +0530 Subject: [PATCH 010/138] Support case insensitive bidder name in ext.prebid.data.eidpermissions.bidders (#3141) --- endpoints/openrtb2/auction.go | 7 +- ...idpermissions-insensitive-bidder-name.json | 109 ++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/eidpermissions-insensitive-bidder-name.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index bede06217e4..810741982ef 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1020,7 +1020,12 @@ func validateBidders(bidders []string, knownBidders map[string]openrtb_ext.Bidde return errors.New(`bidder wildcard "*" mixed with specific bidders`) } } else { - _, isCoreBidder := knownBidders[bidder] + bidderName := bidder + normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidderName) + if ok { + bidderName = normalizedCoreBidder.String() + } + _, isCoreBidder := knownBidders[bidderName] _, isAlias := knownAliases[bidder] if !isCoreBidder && !isAlias { return fmt.Errorf(`unrecognized bidder "%v"`, bidder) diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/eidpermissions-insensitive-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/eidpermissions-insensitive-bidder-name.json new file mode 100644 index 00000000000..b06b2593e26 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/eidpermissions-insensitive-bidder-name.json @@ -0,0 +1,109 @@ +{ + "description": "This demonstrates ext.prebid.data.eidpermissions.bidders with case insensitive bidder names", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "ext": { + "consent": "gdpr-consent-string" + } + }, + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1NYN" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "APPNEXUS": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "data": { + "eidpermissions": [ + { + "source": "source1", + "bidders": [ + "APPNEXUS" + ] + } + ] + }, + "cache": { + "bids": {} + }, + "channel": { + "name": "video", + "version": "1.0" + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1 + } + ], + "seat": "APPNEXUS" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file From 6738217a311ec4f53b10f09ddaf98d619268fd49 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 26 Sep 2023 03:43:57 -0400 Subject: [PATCH 011/138] Remove Adapter: RhythmOne (#3129) --- adapters/rhythmone/params_test.go | 57 ----- adapters/rhythmone/rhythmone.go | 150 ------------- adapters/rhythmone/rhythmone_test.go | 20 -- .../exemplary/banner-and-video-app.json | 211 ------------------ .../exemplary/banner-and-video-gdpr.json | 182 --------------- .../exemplary/banner-and-video-site.json | 190 ---------------- .../exemplary/banner-and-video.json | 191 ---------------- .../exemplary/simple-banner.json | 114 ---------- .../rhythmonetest/exemplary/simple-video.json | 109 --------- .../supplemental/missing-extension.json | 34 --- .../exemplary/simple-banner-both-ids.json | 2 +- .../risetest/exemplary/simple-banner.json | 2 +- .../rise/risetest/exemplary/simple-video.json | 2 +- .../risetest/supplemental/missing-mtype.json | 4 +- exchange/adapter_builders.go | 2 - exchange/adapter_util.go | 1 + openrtb_ext/bidders.go | 2 - openrtb_ext/imp_rhythmone.go | 9 - static/bidder-info/rhythmone.yaml | 17 -- static/bidder-params/rhythmone.json | 24 -- 20 files changed, 6 insertions(+), 1317 deletions(-) delete mode 100644 adapters/rhythmone/params_test.go delete mode 100644 adapters/rhythmone/rhythmone.go delete mode 100644 adapters/rhythmone/rhythmone_test.go delete mode 100644 adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-app.json delete mode 100644 adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-gdpr.json delete mode 100644 adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-site.json delete mode 100644 adapters/rhythmone/rhythmonetest/exemplary/banner-and-video.json delete mode 100644 adapters/rhythmone/rhythmonetest/exemplary/simple-banner.json delete mode 100644 adapters/rhythmone/rhythmonetest/exemplary/simple-video.json delete mode 100644 adapters/rhythmone/rhythmonetest/supplemental/missing-extension.json delete mode 100644 openrtb_ext/imp_rhythmone.go delete mode 100644 static/bidder-info/rhythmone.yaml delete mode 100644 static/bidder-params/rhythmone.json diff --git a/adapters/rhythmone/params_test.go b/adapters/rhythmone/params_test.go deleted file mode 100644 index 7d8cad47d53..00000000000 --- a/adapters/rhythmone/params_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package rhythmone - -import ( - "encoding/json" - "testing" - - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestValidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderRhythmone, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected rhythmone params: %s", validParam) - } - } -} - -func TestInvalidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderRhythmone, json.RawMessage(invalidParam)); err == nil { - t.Errorf("Schema allowed unexpected params: %s", invalidParam) - } - } -} - -var validParams = []string{ - `{"placementId":"123", "zone":"12345", "path":"34567"}`, -} - -var invalidParams = []string{ - `{"placementId":"123", "zone":"12345", "path":34567}`, - `{"placementId":"123", "zone":12345, "path":"34567"}`, - `{"placementId":123, "zone":"12345", "path":"34567"}`, - `{"placementId":123, "zone":12345, "path":34567}`, - `{"placementId":123, "zone":12345, "path":"34567"}`, - `{"appId":"123", "bidfloor":0.01}`, - `{"publisherName": 100}`, - `{"placementId": 1234}`, - `{"zone": true}`, - ``, - `null`, - `nil`, - `true`, - `9`, - `[]`, - `{}`, -} diff --git a/adapters/rhythmone/rhythmone.go b/adapters/rhythmone/rhythmone.go deleted file mode 100644 index 0ac07a77252..00000000000 --- a/adapters/rhythmone/rhythmone.go +++ /dev/null @@ -1,150 +0,0 @@ -package rhythmone - -import ( - "encoding/json" - "fmt" - - "net/http" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" -) - -type RhythmoneAdapter struct { - endPoint string -} - -func (a *RhythmoneAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - errs := make([]error, 0, len(request.Imp)) - - var uri string - request, uri, errs = a.preProcess(request, errs) - if request != nil { - reqJSON, err := json.Marshal(request) - if err != nil { - errs = append(errs, err) - return nil, errs - } - if uri != "" { - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - return []*adapters.RequestData{{ - Method: "POST", - Uri: uri, - Body: reqJSON, - Headers: headers, - }}, errs - } - } - return nil, errs -} - -func (a *RhythmoneAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("bad server response: %d. ", err), - }} - } - - var errs []error - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp), - }) - } - } - return bidResponse, errs -} - -func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { - mediaType := openrtb_ext.BidTypeBanner - for _, imp := range imps { - if imp.ID == impId { - if imp.Banner != nil { - mediaType = openrtb_ext.BidTypeBanner - } else if imp.Video != nil { - mediaType = openrtb_ext.BidTypeVideo - } - return mediaType - } - } - return mediaType -} - -// Builder builds a new instance of the Rythomone adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &RhythmoneAdapter{ - endPoint: config.Endpoint, - } - return bidder, nil -} - -func (a *RhythmoneAdapter) preProcess(req *openrtb2.BidRequest, errors []error) (*openrtb2.BidRequest, string, []error) { - numRequests := len(req.Imp) - var uri string = "" - for i := 0; i < numRequests; i++ { - imp := req.Imp[i] - var bidderExt adapters.ExtImpBidder - err := json.Unmarshal(imp.Ext, &bidderExt) - if err != nil { - err = &errortypes.BadInput{ - Message: fmt.Sprintf("ext data not provided in imp id=%s. Abort all Request", imp.ID), - } - errors = append(errors, err) - return nil, "", errors - } - var rhythmoneExt openrtb_ext.ExtImpRhythmone - err = json.Unmarshal(bidderExt.Bidder, &rhythmoneExt) - if err != nil { - err = &errortypes.BadInput{ - Message: fmt.Sprintf("placementId | zone | path not provided in imp id=%s. Abort all Request", imp.ID), - } - errors = append(errors, err) - return nil, "", errors - } - rhythmoneExt.S2S = true - rhythmoneExtCopy, err := json.Marshal(&rhythmoneExt) - if err != nil { - errors = append(errors, err) - return nil, "", errors - } - bidderExtCopy := struct { - Bidder json.RawMessage `json:"bidder,omitempty"` - }{rhythmoneExtCopy} - impExtCopy, err := json.Marshal(&bidderExtCopy) - if err != nil { - errors = append(errors, err) - return nil, "", errors - } - imp.Ext = impExtCopy - req.Imp[i] = imp - if uri == "" { - uri = fmt.Sprintf("%s/%s/0/%s?z=%s&s2s=%s", a.endPoint, rhythmoneExt.PlacementId, rhythmoneExt.Path, rhythmoneExt.Zone, "true") - } - } - return req, uri, errors -} diff --git a/adapters/rhythmone/rhythmone_test.go b/adapters/rhythmone/rhythmone_test.go deleted file mode 100644 index 0492241dce6..00000000000 --- a/adapters/rhythmone/rhythmone_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package rhythmone - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderRhythmone, config.Adapter{ - Endpoint: "http://tag.1rx.io/rmp"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "rhythmonetest", bidder) -} diff --git a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-app.json b/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-app.json deleted file mode 100644 index 11e89c37007..00000000000 --- a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-app.json +++ /dev/null @@ -1,211 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 1, - "maxduration": 2, - "protocols": [1, 2, 5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1, 2, 3, 4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - } - ], - "app": { - "id": "agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA", - "name": "Yahoo Weather", - "bundle": "12345", - "storeurl": "https://itunes.apple.com/id628677149", - "cat": ["IAB15", "IAB15-10"], - "ver": "1.0.2", - "publisher": { - "id": "1" - } - }, - "device": { - "dnt": 0, - "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 6_1 like Mac OS X) AppleWebKit / 534.46(KHTML, like Gecko) Version / 5.1 Mobile / 9 A334 Safari / 7534.48 .3", - "ip": "123.145.167.189", - "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", - "carrier": "VERIZON", - "language": "en", - "make": "Apple", - "model": "iPhone", - "os": "iOS", - "osv": "6.1", - "js": 1, - "connectiontype": 3, - "devicetype": 1 - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tag.1rx.io/rmp/72721/0/mvo?z=1r&s2s=true", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 1, - "maxduration": 2, - "protocols": [1, 2, 5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1, 2, 3, 4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - } - ], - "app": { - "id": "agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA", - "name": "Yahoo Weather", - "bundle": "12345", - "storeurl": "https://itunes.apple.com/id628677149", - "cat": ["IAB15", "IAB15-10"], - "ver": "1.0.2", - "publisher": { - "id": "1" - } - }, - "device": { - "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 6_1 like Mac OS X) AppleWebKit / 534.46(KHTML, like Gecko) Version / 5.1 Mobile / 9 A334 Safari / 7534.48 .3", - "ip": "123.145.167.189", - "devicetype": 1, - "make": "Apple", - "model": "iPhone", - "os": "iOS", - "osv": "6.1", - "js": 1, - "dnt": 0, - "language": "en", - "carrier": "VERIZON", - "connectiontype": 3, - "ifa": "AA000DFE74168477C70D291f574D344790E0BB11" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [ - { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "h": 576, - "w": 1024 - } - ] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "bids": [ - { - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "w": 1024, - "h": 576 - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-gdpr.json b/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-gdpr.json deleted file mode 100644 index d6546179c24..00000000000 --- a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-gdpr.json +++ /dev/null @@ -1,182 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 1, - "maxduration": 2, - "protocols": [1, 2, 5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1, 2, 3, 4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - } - ], - "user": { - "id": "eyJ0ZW1wVUlEcyI6eyJhZGZvcm0iOnsidWlkIjoiMzA5MTMwOTUxNjQ5NDA1MjcxIiwiZXhwaXJlcyI6IjIwMTgtMDYtMjBUMTE6NDA6MzUuODAwNTE0NzQ3KzA1OjMwIn0sImFkbnhzIjp7InVpZCI6IjM1MTUzMjg2MTAyNjMxNjQ0ODQiLCJleHBpcmVzIjoiMjAxOC0wNi0xOFQxODoxMjoxNy4wMTExMzg2MDgrMDU6MzAifX0sImJkYXkiOiIyMDE4LTA2LTA0VDE4OjEyOjE3LjAxMTEzMDg3NSswNTozMCJ9", - "ext": { - "consent": "BOPVK28OPVK28ABABAENA8-AAAADkCNQCGoQAAQ" - } - }, - "regs": { - "ext": { - "gdpr": 1 - } - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tag.1rx.io/rmp/72721/0/mvo?z=1r&s2s=true", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 1, - "maxduration": 2, - "protocols": [1, 2, 5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1, 2, 3, 4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - } - ], - "user": { - "id": "eyJ0ZW1wVUlEcyI6eyJhZGZvcm0iOnsidWlkIjoiMzA5MTMwOTUxNjQ5NDA1MjcxIiwiZXhwaXJlcyI6IjIwMTgtMDYtMjBUMTE6NDA6MzUuODAwNTE0NzQ3KzA1OjMwIn0sImFkbnhzIjp7InVpZCI6IjM1MTUzMjg2MTAyNjMxNjQ0ODQiLCJleHBpcmVzIjoiMjAxOC0wNi0xOFQxODoxMjoxNy4wMTExMzg2MDgrMDU6MzAifX0sImJkYXkiOiIyMDE4LTA2LTA0VDE4OjEyOjE3LjAxMTEzMDg3NSswNTozMCJ9", - "ext": { - "consent": "BOPVK28OPVK28ABABAENA8-AAAADkCNQCGoQAAQ" - } - }, - "regs": { - "ext": { - "gdpr": 1 - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [ - { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "h": 576, - "w": 1024 - } - ] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "bids": [ - { - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "w": 1024, - "h": 576 - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-site.json b/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-site.json deleted file mode 100644 index 223ebee5fb0..00000000000 --- a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-site.json +++ /dev/null @@ -1,190 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 1, - "maxduration": 2, - "protocols": [1, 2, 5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1, 2, 3, 4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - } - ], - "site": { - "id": "102855", - "cat": ["IAB3-1"], - "domain": "www.foobar.com", - "page": "http://www.foobar.com/1234.html ", - "publisher": { - "id": "8953", - "name": "foobar.com", - "cat": ["IAB3-1"], - "domain": "foobar.com" - } - }, - "device": { - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version / 5.1 .7 Safari / 534.57 .2", - "ip": "123.145.167.10" - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tag.1rx.io/rmp/72721/0/mvo?z=1r&s2s=true", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 1, - "maxduration": 2, - "protocols": [1, 2, 5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1, 2, 3, 4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - } - ], - "site": { - "id": "102855", - "cat": ["IAB3-1"], - "domain": "www.foobar.com", - "page": "http://www.foobar.com/1234.html ", - "publisher": { - "id": "8953", - "name": "foobar.com", - "cat": ["IAB3-1"], - "domain": "foobar.com" - } - }, - "device": { - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version / 5.1 .7 Safari / 534.57 .2", - "ip": "123.145.167.10" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [ - { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "h": 576, - "w": 1024 - } - ] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "w": 1024, - "h": 576 - }, - "type": "video" - }] - } - ] -} diff --git a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video.json b/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video.json deleted file mode 100644 index ae400a2d53c..00000000000 --- a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": [ - "video/mp4" - ], - "minduration": 1, - "maxduration": 2, - "protocols": [ - 1, - 2, - 5 - ], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [ - 2 - ], - "delivery": [ - 1 - ], - "api": [ - 1, - 2, - 3, - 4 - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tag.1rx.io/rmp/72721/0/mvo?z=1r&s2s=true", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": [ - "video/mp4" - ], - "minduration": 1, - "maxduration": 2, - "protocols": [ - 1, - 2, - 5 - ], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [ - 2 - ], - "delivery": [ - 1 - ], - "api": [ - 1, - 2, - 3, - 4 - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [ - { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.500000, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": [ - "yahoo.com" - ], - "cid": "958", - "crid": "29681110", - "h": 576, - "w": 1024 - } - ] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": [ - "yahoo.com" - ], - "cid": "958", - "crid": "29681110", - "w": 1024, - "h": 576 - }, - "type": "video" - }] - } - ] -} \ No newline at end of file diff --git a/adapters/rhythmone/rhythmonetest/exemplary/simple-banner.json b/adapters/rhythmone/rhythmonetest/exemplary/simple-banner.json deleted file mode 100644 index d2acf3612af..00000000000 --- a/adapters/rhythmone/rhythmonetest/exemplary/simple-banner.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tag.1rx.io/rmp/72721/0/mvo?z=1r&s2s=true", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "Rhythmone", - "bid": [ - { - "id": "7706636740145184841", - "impid": "test-imp-id", - "price": 0.500000, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": [ - "yahoo.com" - ], - "cid": "958", - "crid": "29681110", - "h": 250, - "w": 300 - } - ] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": [ - "yahoo.com" - ], - "cid": "958", - "crid": "29681110", - "w": 300, - "h": 250 - }, - "type": "banner" - }] - } - ] -} \ No newline at end of file diff --git a/adapters/rhythmone/rhythmonetest/exemplary/simple-video.json b/adapters/rhythmone/rhythmonetest/exemplary/simple-video.json deleted file mode 100644 index 6b86a9e39ee..00000000000 --- a/adapters/rhythmone/rhythmonetest/exemplary/simple-video.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": [ - "video/mp4" - ], - "minduration": 1, - "maxduration": 2, - "protocols": [1,3,5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1,2,3,4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tag.1rx.io/rmp/72721/0/mvo?z=1r&s2s=true", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": [ - "video/mp4" - ], - "minduration": 1, - "maxduration": 2, - "protocols": [1,3,5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1,2,3,4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "cur": "USD", - "seatbid": [ - { - "seat": "Rhythmone", - "bid": [ - { - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 0.500000, - "adm": "some-test-ad", - "crid": "crid_10", - "w": 1024, - "h": 576 - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 0.5, - "adm": "some-test-ad", - "crid": "crid_10", - "w": 1024, - "h": 576 - }, - "type": "video" - }] - } - ] -} \ No newline at end of file diff --git a/adapters/rhythmone/rhythmonetest/supplemental/missing-extension.json b/adapters/rhythmone/rhythmonetest/supplemental/missing-extension.json deleted file mode 100644 index d313a1759d4..00000000000 --- a/adapters/rhythmone/rhythmonetest/supplemental/missing-extension.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-missing-ext-id", - "video": { - "mimes": [ - "video/mp4" - ], - "minduration": 1, - "maxduration": 2, - "maxextended": 30, - "minbitrate": 300, - "maxbitrate": 1500, - "protocols": [1,2,5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1,2,3,4] - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "ext data not provided in imp id=test-missing-ext-id. Abort all Request", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/rise/risetest/exemplary/simple-banner-both-ids.json b/adapters/rise/risetest/exemplary/simple-banner-both-ids.json index cb6bbfa779f..99f9e1f211f 100644 --- a/adapters/rise/risetest/exemplary/simple-banner-both-ids.json +++ b/adapters/rise/risetest/exemplary/simple-banner-both-ids.json @@ -66,7 +66,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "Rhythmone", + "seat": "958", "bid": [ { "id": "7706636740145184841", diff --git a/adapters/rise/risetest/exemplary/simple-banner.json b/adapters/rise/risetest/exemplary/simple-banner.json index 13e965d1e2f..1fba0f398cb 100644 --- a/adapters/rise/risetest/exemplary/simple-banner.json +++ b/adapters/rise/risetest/exemplary/simple-banner.json @@ -64,7 +64,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "Rhythmone", + "seat": "958", "bid": [ { "id": "7706636740145184841", diff --git a/adapters/rise/risetest/exemplary/simple-video.json b/adapters/rise/risetest/exemplary/simple-video.json index 921736a6295..0854c06c59f 100644 --- a/adapters/rise/risetest/exemplary/simple-video.json +++ b/adapters/rise/risetest/exemplary/simple-video.json @@ -71,7 +71,7 @@ "cur": "USD", "seatbid": [ { - "seat": "Rhythmone", + "seat": "958", "bid": [ { "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", diff --git a/adapters/rise/risetest/supplemental/missing-mtype.json b/adapters/rise/risetest/supplemental/missing-mtype.json index 5693f25ad83..79fe4b38406 100644 --- a/adapters/rise/risetest/supplemental/missing-mtype.json +++ b/adapters/rise/risetest/supplemental/missing-mtype.json @@ -71,7 +71,7 @@ "cur": "USD", "seatbid": [ { - "seat": "Rhythmone", + "seat": "958", "bid": [ { "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", @@ -85,7 +85,7 @@ ] }, { - "seat": "Rhythmone", + "seat": "958", "bid": [ { "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 395e4fbbc39..825eff56353 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -140,7 +140,6 @@ import ( "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/adapters/pwbid" "github.com/prebid/prebid-server/adapters/revcontent" - "github.com/prebid/prebid-server/adapters/rhythmone" "github.com/prebid/prebid-server/adapters/richaudience" "github.com/prebid/prebid-server/adapters/rise" "github.com/prebid/prebid-server/adapters/rtbhouse" @@ -348,7 +347,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderPWBid: pwbid.Builder, openrtb_ext.BidderQuantumdex: apacdex.Builder, openrtb_ext.BidderRevcontent: revcontent.Builder, - openrtb_ext.BidderRhythmone: rhythmone.Builder, openrtb_ext.BidderRichaudience: richaudience.Builder, openrtb_ext.BidderRise: rise.Builder, openrtb_ext.BidderRTBHouse: rtbhouse.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index ee9a066aa58..5a3b8a4ce99 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -118,6 +118,7 @@ func GetDisabledBidderWarningMessages(infos config.BidderInfos) map[string]strin "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, "brightroll": `Bidder "brightroll" is no longer available in Prebid Server. Please update your configuration.`, + "rhythmone": `Bidder "rhythmone" is no longer available in Prebid Server. Please update your configuration.`, } return mergeRemovedAndDisabledBidderWarningMessages(removed, infos) diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index e12316f7e72..f6636a00a48 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -170,7 +170,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderPWBid, BidderQuantumdex, BidderRevcontent, - BidderRhythmone, BidderRichaudience, BidderRise, BidderRTBHouse, @@ -465,7 +464,6 @@ const ( BidderPWBid BidderName = "pwbid" BidderQuantumdex BidderName = "quantumdex" BidderRevcontent BidderName = "revcontent" - BidderRhythmone BidderName = "rhythmone" BidderRichaudience BidderName = "richaudience" BidderRise BidderName = "rise" BidderRTBHouse BidderName = "rtbhouse" diff --git a/openrtb_ext/imp_rhythmone.go b/openrtb_ext/imp_rhythmone.go deleted file mode 100644 index 526a2843b53..00000000000 --- a/openrtb_ext/imp_rhythmone.go +++ /dev/null @@ -1,9 +0,0 @@ -package openrtb_ext - -// ExtImpRhythmone defines the contract for bidrequest.imp[i].ext.prebid.bidder.rhythmone -type ExtImpRhythmone struct { - PlacementId string `json:"placementId"` - Zone string `json:"zone"` - Path string `json:"path"` - S2S bool -} diff --git a/static/bidder-info/rhythmone.yaml b/static/bidder-info/rhythmone.yaml deleted file mode 100644 index 529eae12628..00000000000 --- a/static/bidder-info/rhythmone.yaml +++ /dev/null @@ -1,17 +0,0 @@ -endpoint: "http://tag.1rx.io/rmp" -maintainer: - email: "support@rhythmone.com" -gvlVendorID: 36 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video -userSync: - redirect: - url: "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" - userMacro: "[RX_UUID]" \ No newline at end of file diff --git a/static/bidder-params/rhythmone.json b/static/bidder-params/rhythmone.json deleted file mode 100644 index 01366b45607..00000000000 --- a/static/bidder-params/rhythmone.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Rhythmone Adapter Params", - "description": "A schema which validates params accepted by the Rhythmone adapter", - "type": "object", - "properties": { - "placementId": { - "type": "string", - "description": "An ID which is used to frame Rhythmone ad tag", - "minLength": 1 - }, - "path": { - "type": "string", - "description": "An ID which is used to frame Rhythmone ad tag", - "minLength": 1 - }, - "zone": { - "type": "string", - "description": "An ID which is used to frame Rhythmone ad tag", - "minLength": 1 - } - }, - "required": ["placementId", "path", "zone"] -} From 18edf18b9f0cf28f2c441f47299c3b50ee02caa8 Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Tue, 26 Sep 2023 13:36:19 +0530 Subject: [PATCH 012/138] make NormalizeBidderName to do faster lookup (#3138) --- openrtb_ext/bidders.go | 5 +++-- openrtb_ext/bidders_test.go | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index f6636a00a48..c2c0725b5e7 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -238,6 +238,7 @@ func SetAliasBidderName(aliasBidderName string, parentBidderName BidderName) err aliasBidder := BidderName(aliasBidderName) coreBidderNames = append(coreBidderNames, aliasBidder) aliasBidderToParent[aliasBidder] = parentBidderName + bidderNameLookup[strings.ToLower(aliasBidderName)] = aliasBidder return nil } @@ -562,11 +563,11 @@ var bidderNameLookup = func() map[string]BidderName { lookup[bidderNameLower] = name } return lookup -} +}() func NormalizeBidderName(name string) (BidderName, bool) { nameLower := strings.ToLower(name) - bidderName, exists := bidderNameLookup()[nameLower] + bidderName, exists := bidderNameLookup[nameLower] return bidderName, exists } diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index 2176c28e184..98404a205b1 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "os" + "strings" "testing" "testing/fstest" @@ -148,6 +149,7 @@ func TestSetAliasBidderName(t *testing.T) { } else { assert.Contains(t, CoreBidderNames(), BidderName(test.aliasBidderName)) assert.Contains(t, aliasBidderToParent, BidderName(test.aliasBidderName)) + assert.Contains(t, bidderNameLookup, strings.ToLower(test.aliasBidderName)) } } From d10ccf6f36e104ce0aa9f4a03066999e30ea5769 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 26 Sep 2023 04:38:08 -0400 Subject: [PATCH 013/138] Remove Adapter: Applogy (#3130) Co-authored-by: Ashish Garg --- adapters/applogy/applogy.go | 163 ---------------- adapters/applogy/applogy_test.go | 20 -- .../applogytest/exemplary/simple-banner.json | 170 ---------------- .../applogytest/exemplary/simple-native.json | 164 ---------------- .../applogytest/exemplary/simple-video.json | 182 ------------------ .../applogytest/supplemental/all-failed.json | 16 -- .../supplemental/invalid-params.json | 133 ------------- .../applogytest/supplemental/status-204.json | 42 ---- .../applogytest/supplemental/status-400.json | 45 ----- .../applogytest/supplemental/status-502.json | 45 ----- .../hooks/amp_bidder_reject.json | 20 +- .../hooks/amp_bidder_response_reject.json | 20 +- .../hooks/auction_bidder_reject.json | 10 +- .../hooks/auction_bidder_response_reject.json | 10 +- exchange/adapter_builders.go | 2 - exchange/adapter_util.go | 1 + openrtb_ext/bidders.go | 2 - openrtb_ext/imp_applogy.go | 5 - static/bidder-info/applogy.yaml | 14 -- static/bidder-params/applogy.json | 13 -- 20 files changed, 31 insertions(+), 1046 deletions(-) delete mode 100644 adapters/applogy/applogy.go delete mode 100644 adapters/applogy/applogy_test.go delete mode 100644 adapters/applogy/applogytest/exemplary/simple-banner.json delete mode 100644 adapters/applogy/applogytest/exemplary/simple-native.json delete mode 100644 adapters/applogy/applogytest/exemplary/simple-video.json delete mode 100644 adapters/applogy/applogytest/supplemental/all-failed.json delete mode 100644 adapters/applogy/applogytest/supplemental/invalid-params.json delete mode 100644 adapters/applogy/applogytest/supplemental/status-204.json delete mode 100644 adapters/applogy/applogytest/supplemental/status-400.json delete mode 100644 adapters/applogy/applogytest/supplemental/status-502.json delete mode 100644 openrtb_ext/imp_applogy.go delete mode 100644 static/bidder-info/applogy.yaml delete mode 100644 static/bidder-params/applogy.json diff --git a/adapters/applogy/applogy.go b/adapters/applogy/applogy.go deleted file mode 100644 index 14826ec1873..00000000000 --- a/adapters/applogy/applogy.go +++ /dev/null @@ -1,163 +0,0 @@ -package applogy - -import ( - "encoding/json" - "errors" - "net/http" - "strconv" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" -) - -type ApplogyAdapter struct { - endpoint string -} - -func (a *ApplogyAdapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - impressions := request.Imp - result := make([]*adapters.RequestData, 0, len(impressions)) - errs := make([]error, 0, len(impressions)) - - for _, impression := range impressions { - if impression.Banner == nil && impression.Video == nil && impression.Native == nil { - errs = append(errs, &errortypes.BadInput{ - Message: "Applogy only supports banner, video or native ads", - }) - continue - } - if impression.Banner != nil { - if impression.Banner.W == nil || impression.Banner.H == nil || *impression.Banner.W == 0 || *impression.Banner.H == 0 { - if len(impression.Banner.Format) == 0 { - errs = append(errs, &errortypes.BadInput{ - Message: "banner size information missing", - }) - continue - } - banner := *impression.Banner - banner.W = openrtb2.Int64Ptr(banner.Format[0].W) - banner.H = openrtb2.Int64Ptr(banner.Format[0].H) - impression.Banner = &banner - } - } - if len(impression.Ext) == 0 { - errs = append(errs, errors.New("impression extensions required")) - continue - } - var bidderExt adapters.ExtImpBidder - err := json.Unmarshal(impression.Ext, &bidderExt) - if err != nil { - errs = append(errs, err) - continue - } - if len(bidderExt.Bidder) == 0 { - errs = append(errs, errors.New("bidder required")) - continue - } - var impressionExt openrtb_ext.ExtImpApplogy - err = json.Unmarshal(bidderExt.Bidder, &impressionExt) - if err != nil { - errs = append(errs, err) - continue - } - if impressionExt.Token == "" { - errs = append(errs, errors.New("Applogy token required")) - continue - } - request.Imp = []openrtb2.Imp{impression} - body, err := json.Marshal(request) - if err != nil { - errs = append(errs, err) - continue - } - result = append(result, &adapters.RequestData{ - Method: "POST", - Uri: a.endpoint + "/" + impressionExt.Token, - Body: body, - Headers: headers, - }) - } - - request.Imp = impressions - - if len(result) == 0 { - return nil, errs - } - return result, errs -} - -func (a *ApplogyAdapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { - var errs []error - - switch responseData.StatusCode { - case http.StatusNoContent: - return nil, nil - case http.StatusBadRequest: - return nil, []error{&errortypes.BadInput{ - Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode), - }} - case http.StatusOK: - break - default: - return nil, []error{&errortypes.BadServerResponse{ - Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode), - }} - } - - var bidResponse openrtb2.BidResponse - err := json.Unmarshal(responseData.Body, &bidResponse) - if err != nil { - return nil, []error{&errortypes.BadServerResponse{ - Message: err.Error(), - }} - } - - response := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) - - for _, seatBid := range bidResponse.SeatBid { - for _, bid := range seatBid.Bid { - bid := bid // pin https://github.com/kyoh86/scopelint#whats-this - var bidType openrtb_ext.BidType - for _, impression := range request.Imp { - if impression.ID != bid.ImpID { - continue - } - switch { - case impression.Banner != nil: - bidType = openrtb_ext.BidTypeBanner - case impression.Video != nil: - bidType = openrtb_ext.BidTypeVideo - case impression.Native != nil: - bidType = openrtb_ext.BidTypeNative - } - break - } - if bidType == "" { - errs = append(errs, &errortypes.BadServerResponse{ - Message: "ignoring bid id=" + bid.ID + ", request doesn't contain any valid impression with id=" + bid.ImpID, - }) - continue - } - response.Bids = append(response.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: bidType, - }) - } - } - - return response, errs -} - -// Builder builds a new instance of the Applogy adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &ApplogyAdapter{ - endpoint: config.Endpoint, - } - return bidder, nil -} diff --git a/adapters/applogy/applogy_test.go b/adapters/applogy/applogy_test.go deleted file mode 100644 index 1144f44c83c..00000000000 --- a/adapters/applogy/applogy_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package applogy - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderApplogy, config.Adapter{ - Endpoint: "http://example.com/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "applogytest", bidder) -} diff --git a/adapters/applogy/applogytest/exemplary/simple-banner.json b/adapters/applogy/applogytest/exemplary/simple-banner.json deleted file mode 100644 index a3926dea623..00000000000 --- a/adapters/applogy/applogytest/exemplary/simple-banner.json +++ /dev/null @@ -1,170 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "banner": { - "format": [{ - "h": 250, - "w": 300 - }] - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }, { - "id": "test-impression-id-2", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }, { - "id": "test-impression-id-3", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "token": "test-token-2" - } - } - }] - }, - "httpCalls": [{ - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-1", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "banner": { - "format": [{ - "h": 250, - "w": 300 - }], - "h": 250, - "w": 300 - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id", - "seatbid": [{ - "bid": [{ - "id": "test-bid-id-1", - "impid": "test-impression-id-1", - "price": 1 - }] - }] - } - } - }, { - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-1", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-2", - "banner": { - "h": 250, - "w": 300 - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id-2", - "seatbid": [{ - "bid": [{ - "id": "test-bid-id-2", - "impid": "test-impression-id-2", - "price": 2 - }] - }] - } - } - }, { - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-2", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-3", - "banner": { - "h": 250, - "w": 300 - }, - "ext": { - "bidder": { - "token": "test-token-2" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id-3", - "seatbid": [{ - "bid": [{ - "id": "test-bid-id-3", - "impid": "test-impression-id-3", - "price": 3 - }] - }] - } - } - }], - "expectedBidResponses": [{ - "bids": [{ - "bid": { - "id": "test-bid-id-1", - "impid": "test-impression-id-1", - "price": 1 - }, - "type": "banner" - }] - }, { - "bids": [{ - "bid": { - "id": "test-bid-id-2", - "impid": "test-impression-id-2", - "price": 2 - }, - "type": "banner" - }] - }, { - "bids": [{ - "bid": { - "id": "test-bid-id-3", - "impid": "test-impression-id-3", - "price": 3 - }, - "type": "banner" - }] - }] -} diff --git a/adapters/applogy/applogytest/exemplary/simple-native.json b/adapters/applogy/applogytest/exemplary/simple-native.json deleted file mode 100644 index 84565ec5575..00000000000 --- a/adapters/applogy/applogytest/exemplary/simple-native.json +++ /dev/null @@ -1,164 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "native": { - "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", - "ver": "1.1" - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }, { - "id": "test-impression-id-2", - "native": { - "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", - "ver": "1.1" - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }, { - "id": "test-impression-id-3", - "native": { - "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", - "ver": "1.1" - }, - "ext": { - "bidder": { - "token": "test-token-2" - } - } - }] - }, - "httpCalls": [{ - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-1", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "native": { - "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", - "ver": "1.1" - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id", - "seatbid": [{ - "bid": [{ - "id": "test-bid-id-1", - "impid": "test-impression-id-1", - "price": 1 - }] - }] - } - } - }, { - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-1", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-2", - "native": { - "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", - "ver": "1.1" - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id-2", - "seatbid": [{ - "bid": [{ - "id": "test-bid-id-2", - "impid": "test-impression-id-2", - "price": 2 - }] - }] - } - } - }, { - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-2", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-3", - "native": { - "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", - "ver": "1.1" - }, - "ext": { - "bidder": { - "token": "test-token-2" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id-3", - "seatbid": [{ - "bid": [{ - "id": "test-bid-id-3", - "impid": "test-impression-id-3", - "price": 3 - }] - }] - } - } - }], - "expectedBidResponses": [{ - "bids": [{ - "bid": { - "id": "test-bid-id-1", - "impid": "test-impression-id-1", - "price": 1 - }, - "type": "native" - }] - }, { - "bids": [{ - "bid": { - "id": "test-bid-id-2", - "impid": "test-impression-id-2", - "price": 2 - }, - "type": "native" - }] - }, { - "bids": [{ - "bid": { - "id": "test-bid-id-3", - "impid": "test-impression-id-3", - "price": 3 - }, - "type": "native" - }] - }] -} diff --git a/adapters/applogy/applogytest/exemplary/simple-video.json b/adapters/applogy/applogytest/exemplary/simple-video.json deleted file mode 100644 index 30237cccd10..00000000000 --- a/adapters/applogy/applogytest/exemplary/simple-video.json +++ /dev/null @@ -1,182 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "video": { - "w": 900, - "h": 250, - "mimes": [ - "video/mp4" - ] - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }, { - "id": "test-impression-id-2", - "video": { - "w": 900, - "h": 250, - "mimes": [ - "video/mp4" - ] - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }, { - "id": "test-impression-id-3", - "video": { - "w": 900, - "h": 250, - "mimes": [ - "video/mp4" - ] - }, - "ext": { - "bidder": { - "token": "test-token-2" - } - } - }] - }, - "httpCalls": [{ - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-1", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "video": { - "w": 900, - "h": 250, - "mimes": [ - "video/mp4" - ] - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id", - "seatbid": [{ - "bid": [{ - "id": "test-bid-id-1", - "impid": "test-impression-id-1", - "price": 1 - }] - }] - } - } - }, { - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-1", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-2", - "video": { - "w": 900, - "h": 250, - "mimes": [ - "video/mp4" - ] - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id-2", - "seatbid": [{ - "bid": [{ - "id": "test-bid-id-2", - "impid": "test-impression-id-2", - "price": 2 - }] - }] - } - } - }, { - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-2", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-3", - "video": { - "w": 900, - "h": 250, - "mimes": [ - "video/mp4" - ] - }, - "ext": { - "bidder": { - "token": "test-token-2" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id-3", - "seatbid": [{ - "bid": [{ - "id": "test-bid-id-3", - "impid": "test-impression-id-3", - "price": 3 - }] - }] - } - } - }], - "expectedBidResponses": [{ - "bids": [{ - "bid": { - "id": "test-bid-id-1", - "impid": "test-impression-id-1", - "price": 1 - }, - "type": "video" - }] - }, { - "bids": [{ - "bid": { - "id": "test-bid-id-2", - "impid": "test-impression-id-2", - "price": 2 - }, - "type": "video" - }] - }, { - "bids": [{ - "bid": { - "id": "test-bid-id-3", - "impid": "test-impression-id-3", - "price": 3 - }, - "type": "video" - }] - }] -} diff --git a/adapters/applogy/applogytest/supplemental/all-failed.json b/adapters/applogy/applogytest/supplemental/all-failed.json deleted file mode 100644 index 7f0244afcfb..00000000000 --- a/adapters/applogy/applogytest/supplemental/all-failed.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "banner": { - "w": 300, - "h": 250 - } - }] - }, - "expectedMakeRequestsErrors": [{ - "value": "impression extensions required", - "comparison": "literal" - }] -} diff --git a/adapters/applogy/applogytest/supplemental/invalid-params.json b/adapters/applogy/applogytest/supplemental/invalid-params.json deleted file mode 100644 index 6b5d5e3224d..00000000000 --- a/adapters/applogy/applogytest/supplemental/invalid-params.json +++ /dev/null @@ -1,133 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "banner": {} - }, { - "id": "test-impression-id-2", - "banner": { - "w": 300, - "h": 250 - } - }, { - "id": "test-impression-id-3", - "banner": { - "w": 300, - "h": 250 - }, - "ext": {} - }, { - "id": "test-impression-id-4" - }, { - "id": "test-impression-id-5", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "token": "test-token-5" - } - } - }, { - "id": "test-impression-id-0", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": "invalid bidder" - } - }, { - "id": "test-impression-id-0", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": {} - } - }, { - "id": "test-impression-id-0", - "banner": { - "w": 300, - "h": 250 - }, - "ext": "invalid ext" - }] - }, - "httpCalls": [{ - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-5", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-5", - "banner": { - "h": 250, - "w": 300 - }, - "ext": { - "bidder": { - "token": "test-token-5" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id", - "seatbid": [{ - "bid": [{ - "id": "test-bid-id-5", - "impid": "test-impression-id-5", - "price": 5 - }, { - "id": "test-bid-id-6", - "impid": "test-impression-id-6", - "price": 6 - }] - }] - } - } - }], - "expectedBidResponses": [{ - "bids": [{ - "bid": { - "id": "test-bid-id-5", - "impid": "test-impression-id-5", - "price": 5 - }, - "type": "banner" - }] - }], - "expectedMakeRequestsErrors": [{ - "value": "banner size information missing", - "comparison": "literal" - }, { - "value": "impression extensions required", - "comparison": "literal" - }, { - "value": "bidder required", - "comparison": "literal" - }, { - "value": "Applogy only supports banner, video or native ads", - "comparison": "literal" - }, { - "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpApplogy", - "comparison": "literal" - }, { - "value": "Applogy token required", - "comparison": "literal" - }, { - "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", - "comparison": "literal" - }], - "expectedMakeBidsErrors": [{ - "value": "ignoring bid id=test-bid-id-6, request doesn't contain any valid impression with id=test-impression-id-6", - "comparison": "literal" - }] -} diff --git a/adapters/applogy/applogytest/supplemental/status-204.json b/adapters/applogy/applogytest/supplemental/status-204.json deleted file mode 100644 index 1c849b4fa3a..00000000000 --- a/adapters/applogy/applogytest/supplemental/status-204.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }] - }, - "httpCalls": [{ - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-1", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "banner": { - "h": 250, - "w": 300 - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }] - } - }, - "mockResponse": { - "status": 204, - "body": {} - } - }], - "expectedBidResponses": [] -} diff --git a/adapters/applogy/applogytest/supplemental/status-400.json b/adapters/applogy/applogytest/supplemental/status-400.json deleted file mode 100644 index 95e271bae8e..00000000000 --- a/adapters/applogy/applogytest/supplemental/status-400.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }] - }, - "httpCalls": [{ - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-1", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "banner": { - "h": 250, - "w": 300 - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }] - } - }, - "mockResponse": { - "status": 400, - "body": {} - } - }], - "expectedMakeBidsErrors": [{ - "value": "unexpected status code: 400", - "comparison": "literal" - }] -} diff --git a/adapters/applogy/applogytest/supplemental/status-502.json b/adapters/applogy/applogytest/supplemental/status-502.json deleted file mode 100644 index c0b1641653a..00000000000 --- a/adapters/applogy/applogytest/supplemental/status-502.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }] - }, - "httpCalls": [{ - "expectedRequest": { - "uri": "http://example.com/prebid/test-token-1", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-impression-id-1", - "banner": { - "h": 250, - "w": 300 - }, - "ext": { - "bidder": { - "token": "test-token-1" - } - } - }] - } - }, - "mockResponse": { - "status": 502, - "body": {} - } - }], - "expectedMakeBidsErrors": [{ - "value": "unexpected status code: 502", - "comparison": "literal" - }] -} diff --git a/endpoints/openrtb2/sample-requests/hooks/amp_bidder_reject.json b/endpoints/openrtb2/sample-requests/hooks/amp_bidder_reject.json index c3ce226ff9c..7fc1e7d8721 100644 --- a/endpoints/openrtb2/sample-requests/hooks/amp_bidder_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/amp_bidder_reject.json @@ -4,7 +4,7 @@ "config": { "mockBidders": [ {"bidderName": "appnexus", "currency": "USD", "price": 2}, - {"bidderName": "applogy", "currency": "USD", "price": 1} + {"bidderName": "rubicon", "currency": "USD", "price": 1} ] }, "mockBidRequest": { @@ -29,7 +29,7 @@ "appnexus": { "placementId": 12883451 }, - "applogy": { + "rubicon": { "placementId": 12883451 } } @@ -42,23 +42,23 @@ "debug": true, "aliases": { "unknown": "appnexus", - "foo": "applogy" + "foo": "rubicon" } } } }, "expectedAmpResponse": { "targeting": { - "hb_bidder": "applogy", - "hb_bidder_applogy": "applogy", + "hb_bidder": "rubicon", + "hb_bidder_rubicon": "rubicon", "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_applog": "www.pbcserver.com", + "hb_cache_host_rubico": "www.pbcserver.com", "hb_cache_id": "0", - "hb_cache_id_applogy": "0", + "hb_cache_id_rubicon": "0", "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_applog": "/pbcache/endpoint", + "hb_cache_path_rubico": "/pbcache/endpoint", "hb_pb": "1.00", - "hb_pb_applogy": "1.00" + "hb_pb_rubicon": "1.00" }, "ortb2": { "ext": { @@ -103,7 +103,7 @@ ] }, { - "entity": "applogy", + "entity": "rubicon", "groups": [ { "invocation_results": [ diff --git a/endpoints/openrtb2/sample-requests/hooks/amp_bidder_response_reject.json b/endpoints/openrtb2/sample-requests/hooks/amp_bidder_response_reject.json index fe654304c4f..4e38b5a2f2d 100644 --- a/endpoints/openrtb2/sample-requests/hooks/amp_bidder_response_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/amp_bidder_response_reject.json @@ -4,7 +4,7 @@ "config": { "mockBidders": [ {"bidderName": "appnexus", "currency": "USD", "price": 2}, - {"bidderName": "applogy", "currency": "USD", "price": 1} + {"bidderName": "rubicon", "currency": "USD", "price": 1} ] }, "mockBidRequest": { @@ -29,7 +29,7 @@ "appnexus": { "placementId": 12883451 }, - "applogy": { + "rubicon": { "placementId": 12883451 } } @@ -42,23 +42,23 @@ "debug": true, "aliases": { "unknown": "appnexus", - "foo": "applogy" + "foo": "rubicon" } } } }, "expectedAmpResponse": { "targeting": { - "hb_bidder": "applogy", - "hb_bidder_applogy": "applogy", + "hb_bidder": "rubicon", + "hb_bidder_rubicon": "rubicon", "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_applog": "www.pbcserver.com", + "hb_cache_host_rubico": "www.pbcserver.com", "hb_cache_id": "0", - "hb_cache_id_applogy": "0", + "hb_cache_id_rubicon": "0", "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_applog": "/pbcache/endpoint", + "hb_cache_path_rubico": "/pbcache/endpoint", "hb_pb": "1.00", - "hb_pb_applogy": "1.00" + "hb_pb_rubicon": "1.00" }, "ortb2": { "ext": { @@ -103,7 +103,7 @@ ] }, { - "entity": "applogy", + "entity": "rubicon", "groups": [ { "invocation_results": [ diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json index bece42277ef..94769e4f6cd 100644 --- a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json @@ -3,7 +3,7 @@ "config": { "mockBidders": [ {"bidderName": "appnexus", "currency": "USD", "price": 0.00}, - {"bidderName": "applogy", "currency": "USD", "price": 0.00} + {"bidderName": "rubicon", "currency": "USD", "price": 0.00} ] }, "mockBidRequest": { @@ -26,7 +26,7 @@ "appnexus": { "placementId": 12883451 }, - "applogy": { + "rubicon": { "placementId": 12883451 } } @@ -49,12 +49,12 @@ { "bid": [ { - "id": "applogy-bid", + "id": "rubicon-bid", "impid": "some-impression-id", "price": 0 } ], - "seat": "applogy" + "seat": "rubicon" } ], "ext": { @@ -99,7 +99,7 @@ ] }, { - "entity": "applogy", + "entity": "rubicon", "groups": [ { "invocation_results": [ diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json index 32743d67b75..6198e3e23bc 100644 --- a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json @@ -3,7 +3,7 @@ "config": { "mockBidders": [ {"bidderName": "appnexus", "currency": "USD", "price": 0.00}, - {"bidderName": "applogy", "currency": "USD", "price": 0.00} + {"bidderName": "rubicon", "currency": "USD", "price": 0.00} ] }, "mockBidRequest": { @@ -26,7 +26,7 @@ "appnexus": { "placementId": 12883451 }, - "applogy": { + "rubicon": { "placementId": 12883451 } } @@ -49,12 +49,12 @@ { "bid": [ { - "id": "applogy-bid", + "id": "rubicon-bid", "impid": "some-impression-id", "price": 0 } ], - "seat": "applogy" + "seat": "rubicon" } ], "ext": { @@ -99,7 +99,7 @@ ] }, { - "entity": "applogy", + "entity": "rubicon", "groups": [ { "invocation_results": [ diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 825eff56353..8e80070bd79 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -34,7 +34,6 @@ import ( "github.com/prebid/prebid-server/adapters/algorix" "github.com/prebid/prebid-server/adapters/amx" "github.com/prebid/prebid-server/adapters/apacdex" - "github.com/prebid/prebid-server/adapters/applogy" "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/appush" "github.com/prebid/prebid-server/adapters/audienceNetwork" @@ -229,7 +228,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAlgorix: algorix.Builder, openrtb_ext.BidderAMX: amx.Builder, openrtb_ext.BidderApacdex: apacdex.Builder, - openrtb_ext.BidderApplogy: applogy.Builder, openrtb_ext.BidderAppnexus: appnexus.Builder, openrtb_ext.BidderAppstock: limelightDigital.Builder, openrtb_ext.BidderAppush: appush.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 5a3b8a4ce99..f6c1ffe8b55 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -118,6 +118,7 @@ func GetDisabledBidderWarningMessages(infos config.BidderInfos) map[string]strin "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, "brightroll": `Bidder "brightroll" is no longer available in Prebid Server. Please update your configuration.`, + "applogy": `Bidder "applogy" is no longer available in Prebid Server. Please update your configuration.`, "rhythmone": `Bidder "rhythmone" is no longer available in Prebid Server. Please update your configuration.`, } diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index c2c0725b5e7..e3379358012 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -52,7 +52,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderAlgorix, BidderAMX, BidderApacdex, - BidderApplogy, BidderAppnexus, BidderAppstock, BidderAppush, @@ -347,7 +346,6 @@ const ( BidderAlgorix BidderName = "algorix" BidderAMX BidderName = "amx" BidderApacdex BidderName = "apacdex" - BidderApplogy BidderName = "applogy" BidderAppnexus BidderName = "appnexus" BidderAppstock BidderName = "appstock" BidderAppush BidderName = "appush" diff --git a/openrtb_ext/imp_applogy.go b/openrtb_ext/imp_applogy.go deleted file mode 100644 index 45774a05afb..00000000000 --- a/openrtb_ext/imp_applogy.go +++ /dev/null @@ -1,5 +0,0 @@ -package openrtb_ext - -type ExtImpApplogy struct { - Token string `json:"token"` -} diff --git a/static/bidder-info/applogy.yaml b/static/bidder-info/applogy.yaml deleted file mode 100644 index 62a70a17f28..00000000000 --- a/static/bidder-info/applogy.yaml +++ /dev/null @@ -1,14 +0,0 @@ -endpoint: "http://rtb.applogy.com/v1/prebid" -maintainer: - email: work@applogy.com -capabilities: - app: - mediaTypes: - - banner - - video - - native - site: - mediaTypes: - - banner - - video - - native diff --git a/static/bidder-params/applogy.json b/static/bidder-params/applogy.json deleted file mode 100644 index 2650640c115..00000000000 --- a/static/bidder-params/applogy.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Applogy Adapter Params", - "description": "A schema which validates params accepted by the Applogy adapter", - "type": "object", - "properties": { - "token": { - "type": "string", - "description": "Applogy token" - } - }, - "required": ["token"] -} From aff482fb9476391e899feffd929503b3042bb810 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 26 Sep 2023 05:08:07 -0400 Subject: [PATCH 014/138] Remove Adapter: Define Media (#3132) Co-authored-by: Ashish Garg --- adapters/definemedia/definemedia.go | 109 ------- adapters/definemedia/definemedia_test.go | 21 -- .../exemplary/sample-conative-banner.json | 270 ------------------ .../exemplary/sample-conative-native.json | 257 ----------------- .../supplemental/nobid-response.json | 222 -------------- .../supplemental/nocontent-response.json | 219 -------------- .../supplemental/status_400.json | 224 --------------- .../supplemental/status_418.json | 224 --------------- .../supplemental/unmarshal-error.json | 224 --------------- .../supplemental/unsupported-type.json | 250 ---------------- adapters/definemedia/params_test.go | 48 ---- exchange/adapter_builders.go | 2 - exchange/adapter_util.go | 1 + openrtb_ext/bidders.go | 2 - openrtb_ext/imp_definemedia.go | 6 - static/bidder-info/definemedia.yaml | 10 - static/bidder-params/definemedia.json | 19 -- 17 files changed, 1 insertion(+), 2107 deletions(-) delete mode 100644 adapters/definemedia/definemedia.go delete mode 100644 adapters/definemedia/definemedia_test.go delete mode 100644 adapters/definemedia/definemediatest/exemplary/sample-conative-banner.json delete mode 100644 adapters/definemedia/definemediatest/exemplary/sample-conative-native.json delete mode 100644 adapters/definemedia/definemediatest/supplemental/nobid-response.json delete mode 100644 adapters/definemedia/definemediatest/supplemental/nocontent-response.json delete mode 100644 adapters/definemedia/definemediatest/supplemental/status_400.json delete mode 100644 adapters/definemedia/definemediatest/supplemental/status_418.json delete mode 100644 adapters/definemedia/definemediatest/supplemental/unmarshal-error.json delete mode 100644 adapters/definemedia/definemediatest/supplemental/unsupported-type.json delete mode 100644 adapters/definemedia/params_test.go delete mode 100644 openrtb_ext/imp_definemedia.go delete mode 100644 static/bidder-info/definemedia.yaml delete mode 100644 static/bidder-params/definemedia.json diff --git a/adapters/definemedia/definemedia.go b/adapters/definemedia/definemedia.go deleted file mode 100644 index 3e014e3c16d..00000000000 --- a/adapters/definemedia/definemedia.go +++ /dev/null @@ -1,109 +0,0 @@ -package definemedia - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" -) - -type adapter struct { - endpoint string -} - -// Builder builds a new instance of the Foo adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &adapter{ - endpoint: config.Endpoint, - } - return bidder, nil -} - -// MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var errors []error - requestJSON, err := json.Marshal(request) - if err != nil { - errors = append(errors, err) - return nil, errors - } - - requestData := &adapters.RequestData{ - Method: "POST", - Uri: a.endpoint, - Body: requestJSON, - } - - return []*adapters.RequestData{requestData}, errors - -} - -func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{err} - } - - bidsCapacity := 1 - if len(bidResp.SeatBid) > 0 { - bidsCapacity = len(bidResp.SeatBid[0].Bid) - } - bidResponse := adapters.NewBidderResponseWithBidsCapacity(bidsCapacity) - var errors []error - for _, sb := range bidResp.SeatBid { - for i, bid := range sb.Bid { - bidType, err := getMediaTypeForBid(bid) - if err != nil { - errors = append(errors, err) - continue - } - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: bidType, - }) - - } - } - - return bidResponse, errors -} - -func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { - if bid.Ext != nil { - var bidExt openrtb_ext.ExtBid - err := json.Unmarshal(bid.Ext, &bidExt) - if err == nil && bidExt.Prebid != nil { - if (bidExt.Prebid.Type == openrtb_ext.BidTypeBanner) || (bidExt.Prebid.Type == openrtb_ext.BidTypeNative) { - return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)) - } - return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Invalid mediatype in the impression"), - } - } - } - - return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Failed to parse impression \"%s\" mediatype", bid.ImpID), - } -} diff --git a/adapters/definemedia/definemedia_test.go b/adapters/definemedia/definemedia_test.go deleted file mode 100644 index 3ed0cb938b8..00000000000 --- a/adapters/definemedia/definemedia_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package definemedia - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderDefinemedia, config.Adapter{ - Endpoint: "https://rtb.conative.network/openrtb2/auction"}, - config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "definemediatest", bidder) -} diff --git a/adapters/definemedia/definemediatest/exemplary/sample-conative-banner.json b/adapters/definemedia/definemediatest/exemplary/sample-conative-banner.json deleted file mode 100644 index a7d7e411f78..00000000000 --- a/adapters/definemedia/definemediatest/exemplary/sample-conative-banner.json +++ /dev/null @@ -1,270 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", - "prebid": { - "bidder": { - "definemedia": { - "mandantId": 12 - } - } - } - }, - "id": "div-gpt-ad-1460505748561-0", - "banner": { - "topframe": 1, - "format": [ - { - "w": 300, - "h": 250 - } - ] - } - } - ], - "site": { - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "domain": "localhost:8080", - "publisher": { - "domain": "localhost:8080", - "id": "1" - } - }, - "device": { - "w": 1098, - "h": 1169, - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "language": "de", - "sua": { - "source": 2, - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "mobile": 0, - "model": "", - "bitness": "64", - "architecture": "x86" - } - }, - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "test": 0, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1671449004622, - "targeting": { - "includewinners": true, - "includebidderkeys": false - }, - "debug": true, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - } - } - }, - "tmax": 1000 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://rtb.conative.network/openrtb2/auction", - "body": { - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "imp": [ - { - "id": "div-gpt-ad-1460505748561-0", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ], - "topframe": 1 - }, - "ext": { - "prebid": { - "bidder": { - "definemedia": { - "mandantId": 12 - } - } - }, - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" - } - } - ], - "site": { - "domain": "localhost:8080", - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "publisher": { - "id": "1", - "domain": "localhost:8080" - } - }, - "device": { - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "sua": { - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "mobile": 0, - "architecture": "x86", - "bitness": "64", - "source": 2 - }, - "h": 1169, - "w": 1098, - "language": "de" - }, - "tmax": 1000, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1.671449004622e+12, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - }, - "debug": true, - "targeting": { - "includebidderkeys": false, - "includewinners": true - } - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "46189656-7e2e-477d-b7f2-e05de224bb89", - "impid": "div-gpt-ad-1460505748561-0", - "price": 100, - "adm": "{banner html}", - "adomain": [ - "test.com" - ], - "crid": "test-creative-id", - "ext": { - "prebid": { - "type": "banner" - } - } - } - ] - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "46189656-7e2e-477d-b7f2-e05de224bb89", - "impid": "div-gpt-ad-1460505748561-0", - "price": 100, - "adm": "{banner html}", - "crid": "test-creative-id", - "adomain": [ - "test.com" - ], - "ext": { - "prebid": { - "type": "banner" - } - } - }, - "type": "banner" - } - ] - } - ] -} \ No newline at end of file diff --git a/adapters/definemedia/definemediatest/exemplary/sample-conative-native.json b/adapters/definemedia/definemediatest/exemplary/sample-conative-native.json deleted file mode 100644 index 4fe56a4c22e..00000000000 --- a/adapters/definemedia/definemediatest/exemplary/sample-conative-native.json +++ /dev/null @@ -1,257 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", - "prebid": { - "bidder": { - "definemedia": { - "mandantId": 12 - } - } - } - }, - "id": "div-gpt-ad-1460505748561-0", - "native": {} - } - ], - "site": { - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "domain": "localhost:8080", - "publisher": { - "domain": "localhost:8080", - "id": "1" - } - }, - "device": { - "w": 1098, - "h": 1169, - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "language": "de", - "sua": { - "source": 2, - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "mobile": 0, - "model": "", - "bitness": "64", - "architecture": "x86" - } - }, - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "test": 0, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1671449004622, - "targeting": { - "includewinners": true, - "includebidderkeys": false - }, - "debug": true, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - } - } - }, - "tmax": 1000 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://rtb.conative.network/openrtb2/auction", - "body": { - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "imp": [ - { - "id": "div-gpt-ad-1460505748561-0", - "native": { - "request": "" - }, - "ext": { - "prebid": { - "bidder": { - "definemedia": { - "mandantId": 12 - } - } - }, - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" - } - } - ], - "site": { - "domain": "localhost:8080", - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "publisher": { - "id": "1", - "domain": "localhost:8080" - } - }, - "device": { - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "sua": { - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "mobile": 0, - "architecture": "x86", - "bitness": "64", - "source": 2 - }, - "h": 1169, - "w": 1098, - "language": "de" - }, - "tmax": 1000, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1.671449004622e+12, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - }, - "debug": true, - "targeting": { - "includebidderkeys": false, - "includewinners": true - } - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "46189656-7e2e-477d-b7f2-e05de224bb89", - "impid": "div-gpt-ad-1460505748561-0", - "price": 100, - "adm": "{\n \"ver\": \"1.1\",\n \"imptrackers\": [\"http://imptracker.com\"],\n \"jstracker\": \"\u003cscript\u003etrack()\u003c/script\u003e\",\n \"link\": {\n \"url\": \"http://i.am.a/URL\"\n },\n \"assets\": [\n {\n \"id\": 123,\n \"required\": 1,\n \"title\": {\n \"text\": \"Learn about this awesome thing\"\n }\n },\n {\n \"id\": 124,\n \"required\": 1,\n \"img\": {\n \"url\": \"http://www.myads.com/thumbnail1.png\"\n }\n },\n {\n \"id\": 128,\n \"required\": 1,\n \"img\": {\n \"url\": \"http://www.myads.com/largethumb1.png\"\n }\n },\n {\n \"id\": 126,\n \"required\": 1,\n \"data\": {\n \"value\": \"My Brand\"\n }\n },\n {\n \"id\": 127,\n \"required\": 1,\n \"data\": {\n \"value\": \"Learn all about this awesome story of someone using my product.\"\n }\n },\n {\n \"id\": 4,\n \"video\": {\n \"vasttag\": \"\u003cVAST version=\\\"2.0\\\"\u003e\u003c/VAST\u003e\"\n }\n },\n {\n \"id\": 5,\n \"link\": {\n \"url\": \"http://landing.com\",\n \"clicktrackers\": [\"http://tracker.com\"],\n \"fallback\": \"http://fallback.com\"\n }\n }\n ]\n}", - "adomain": [ - "test.com" - ], - "crid": "test-creative-id", - "ext": { - "prebid": { - "type": "native" - } - } - } - ] - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "46189656-7e2e-477d-b7f2-e05de224bb89", - "impid": "div-gpt-ad-1460505748561-0", - "price": 100, - - "adm": "{\n \"ver\": \"1.1\",\n \"imptrackers\": [\"http://imptracker.com\"],\n \"jstracker\": \"\u003cscript\u003etrack()\u003c/script\u003e\",\n \"link\": {\n \"url\": \"http://i.am.a/URL\"\n },\n \"assets\": [\n {\n \"id\": 123,\n \"required\": 1,\n \"title\": {\n \"text\": \"Learn about this awesome thing\"\n }\n },\n {\n \"id\": 124,\n \"required\": 1,\n \"img\": {\n \"url\": \"http://www.myads.com/thumbnail1.png\"\n }\n },\n {\n \"id\": 128,\n \"required\": 1,\n \"img\": {\n \"url\": \"http://www.myads.com/largethumb1.png\"\n }\n },\n {\n \"id\": 126,\n \"required\": 1,\n \"data\": {\n \"value\": \"My Brand\"\n }\n },\n {\n \"id\": 127,\n \"required\": 1,\n \"data\": {\n \"value\": \"Learn all about this awesome story of someone using my product.\"\n }\n },\n {\n \"id\": 4,\n \"video\": {\n \"vasttag\": \"\u003cVAST version=\\\"2.0\\\"\u003e\u003c/VAST\u003e\"\n }\n },\n {\n \"id\": 5,\n \"link\": {\n \"url\": \"http://landing.com\",\n \"clicktrackers\": [\"http://tracker.com\"],\n \"fallback\": \"http://fallback.com\"\n }\n }\n ]\n}", - "crid": "test-creative-id", - "adomain": [ - "test.com" - ], - "ext": { - "prebid": { - "type": "native" - } - } - }, - "type": "native" - } - ] - } - ] -} \ No newline at end of file diff --git a/adapters/definemedia/definemediatest/supplemental/nobid-response.json b/adapters/definemedia/definemediatest/supplemental/nobid-response.json deleted file mode 100644 index aaa942da76e..00000000000 --- a/adapters/definemedia/definemediatest/supplemental/nobid-response.json +++ /dev/null @@ -1,222 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", - "prebid": { - "bidder": { - "definemedia": {} - } - } - }, - "id": "div-gpt-ad-1460505748561-0", - "banner": { - "topframe": 1, - "format": [ - { - "w": 300, - "h": 250 - } - ] - } - } - ], - "site": { - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "domain": "localhost:8080", - "publisher": { - "domain": "localhost:8080", - "id": "1" - } - }, - "device": { - "w": 1098, - "h": 1169, - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "language": "de", - "sua": { - "source": 2, - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "mobile": 0, - "model": "", - "bitness": "64", - "architecture": "x86" - } - }, - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "test": 0, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1671449004622, - "targeting": { - "includewinners": true, - "includebidderkeys": false - }, - "debug": true, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - } - } - }, - "tmax": 1000 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://rtb.conative.network/openrtb2/auction", - "body": { - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "imp": [ - { - "id": "div-gpt-ad-1460505748561-0", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ], - "topframe": 1 - }, - "ext": { - "prebid": { - "bidder": { - "definemedia": {} - } - }, - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" - } - } - ], - "site": { - "domain": "localhost:8080", - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "publisher": { - "id": "1", - "domain": "localhost:8080" - } - }, - "device": { - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "sua": { - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "mobile": 0, - "architecture": "x86", - "bitness": "64", - "source": 2 - }, - "h": 1169, - "w": 1098, - "language": "de" - }, - "tmax": 1000, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1.671449004622e+12, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - }, - "debug": true, - "targeting": { - "includebidderkeys": false, - "includewinners": true - } - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": null, - "cur": null - } - } - } - ], - "expectedBidResponses": [{"currency":"USD","bids":[]}] - } diff --git a/adapters/definemedia/definemediatest/supplemental/nocontent-response.json b/adapters/definemedia/definemediatest/supplemental/nocontent-response.json deleted file mode 100644 index 147d2185e1f..00000000000 --- a/adapters/definemedia/definemediatest/supplemental/nocontent-response.json +++ /dev/null @@ -1,219 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", - "prebid": { - "bidder": { - "definemedia": {} - } - } - }, - "id": "div-gpt-ad-1460505748561-0", - "banner": { - "topframe": 1, - "format": [ - { - "w": 300, - "h": 250 - } - ] - } - } - ], - "site": { - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "domain": "localhost:8080", - "publisher": { - "domain": "localhost:8080", - "id": "1" - } - }, - "device": { - "w": 1098, - "h": 1169, - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "language": "de", - "sua": { - "source": 2, - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "mobile": 0, - "model": "", - "bitness": "64", - "architecture": "x86" - } - }, - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "test": 0, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1671449004622, - "targeting": { - "includewinners": true, - "includebidderkeys": false - }, - "debug": true, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - } - } - }, - "tmax": 1000 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://rtb.conative.network/openrtb2/auction", - "body": { - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "imp": [ - { - "id": "div-gpt-ad-1460505748561-0", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ], - "topframe": 1 - }, - "ext": { - "prebid": { - "bidder": { - "definemedia": {} - } - }, - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" - } - } - ], - "site": { - "domain": "localhost:8080", - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "publisher": { - "id": "1", - "domain": "localhost:8080" - } - }, - "device": { - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "sua": { - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "mobile": 0, - "architecture": "x86", - "bitness": "64", - "source": 2 - }, - "h": 1169, - "w": 1098, - "language": "de" - }, - "tmax": 1000, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1.671449004622e+12, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - }, - "debug": true, - "targeting": { - "includebidderkeys": false, - "includewinners": true - } - } - } - } - }, - "mockResponse": { - "status": 204, - "body": {} - } - } - ], - "expectedBidResponses": [], - "expectedMakeBidsErrors": [] - } \ No newline at end of file diff --git a/adapters/definemedia/definemediatest/supplemental/status_400.json b/adapters/definemedia/definemediatest/supplemental/status_400.json deleted file mode 100644 index ec772bf8428..00000000000 --- a/adapters/definemedia/definemediatest/supplemental/status_400.json +++ /dev/null @@ -1,224 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", - "prebid": { - "bidder": { - "definemedia": {} - } - } - }, - "id": "div-gpt-ad-1460505748561-0", - "banner": { - "topframe": 1, - "format": [ - { - "w": 300, - "h": 250 - } - ] - } - } - ], - "site": { - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "domain": "localhost:8080", - "publisher": { - "domain": "localhost:8080", - "id": "1" - } - }, - "device": { - "w": 1098, - "h": 1169, - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "language": "de", - "sua": { - "source": 2, - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "mobile": 0, - "model": "", - "bitness": "64", - "architecture": "x86" - } - }, - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "test": 0, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1671449004622, - "targeting": { - "includewinners": true, - "includebidderkeys": false - }, - "debug": true, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - } - } - }, - "tmax": 1000 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://rtb.conative.network/openrtb2/auction", - "body": { - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "imp": [ - { - "id": "div-gpt-ad-1460505748561-0", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ], - "topframe": 1 - }, - "ext": { - "prebid": { - "bidder": { - "definemedia": {} - } - }, - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" - } - } - ], - "site": { - "domain": "localhost:8080", - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "publisher": { - "id": "1", - "domain": "localhost:8080" - } - }, - "device": { - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "sua": { - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "mobile": 0, - "architecture": "x86", - "bitness": "64", - "source": 2 - }, - "h": 1169, - "w": 1098, - "language": "de" - }, - "tmax": 1000, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1.671449004622e+12, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - }, - "debug": true, - "targeting": { - "includebidderkeys": false, - "includewinners": true - } - } - } - } - }, - "mockResponse": { - "status": 400, - "body": {} - } - } - ], - "expectedBidResponses": [], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] - } \ No newline at end of file diff --git a/adapters/definemedia/definemediatest/supplemental/status_418.json b/adapters/definemedia/definemediatest/supplemental/status_418.json deleted file mode 100644 index 6e82f90476e..00000000000 --- a/adapters/definemedia/definemediatest/supplemental/status_418.json +++ /dev/null @@ -1,224 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", - "prebid": { - "bidder": { - "definemedia": {} - } - } - }, - "id": "div-gpt-ad-1460505748561-0", - "banner": { - "topframe": 1, - "format": [ - { - "w": 300, - "h": 250 - } - ] - } - } - ], - "site": { - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "domain": "localhost:8080", - "publisher": { - "domain": "localhost:8080", - "id": "1" - } - }, - "device": { - "w": 1098, - "h": 1169, - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "language": "de", - "sua": { - "source": 2, - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "mobile": 0, - "model": "", - "bitness": "64", - "architecture": "x86" - } - }, - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "test": 0, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1671449004622, - "targeting": { - "includewinners": true, - "includebidderkeys": false - }, - "debug": true, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - } - } - }, - "tmax": 1000 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://rtb.conative.network/openrtb2/auction", - "body": { - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "imp": [ - { - "id": "div-gpt-ad-1460505748561-0", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ], - "topframe": 1 - }, - "ext": { - "prebid": { - "bidder": { - "definemedia": {} - } - }, - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" - } - } - ], - "site": { - "domain": "localhost:8080", - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "publisher": { - "id": "1", - "domain": "localhost:8080" - } - }, - "device": { - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "sua": { - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "mobile": 0, - "architecture": "x86", - "bitness": "64", - "source": 2 - }, - "h": 1169, - "w": 1098, - "language": "de" - }, - "tmax": 1000, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1.671449004622e+12, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - }, - "debug": true, - "targeting": { - "includebidderkeys": false, - "includewinners": true - } - } - } - } - }, - "mockResponse": { - "status": 418, - "body": {} - } - } - ], - "expectedBidResponses": [], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 418. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] - } \ No newline at end of file diff --git a/adapters/definemedia/definemediatest/supplemental/unmarshal-error.json b/adapters/definemedia/definemediatest/supplemental/unmarshal-error.json deleted file mode 100644 index 396d6821c36..00000000000 --- a/adapters/definemedia/definemediatest/supplemental/unmarshal-error.json +++ /dev/null @@ -1,224 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", - "prebid": { - "bidder": { - "definemedia": {} - } - } - }, - "id": "div-gpt-ad-1460505748561-0", - "banner": { - "topframe": 1, - "format": [ - { - "w": 300, - "h": 250 - } - ] - } - } - ], - "site": { - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "domain": "localhost:8080", - "publisher": { - "domain": "localhost:8080", - "id": "1" - } - }, - "device": { - "w": 1098, - "h": 1169, - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "language": "de", - "sua": { - "source": 2, - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "mobile": 0, - "model": "", - "bitness": "64", - "architecture": "x86" - } - }, - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "test": 0, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1671449004622, - "targeting": { - "includewinners": true, - "includebidderkeys": false - }, - "debug": true, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - } - } - }, - "tmax": 1000 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://rtb.conative.network/openrtb2/auction", - "body": { - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "imp": [ - { - "id": "div-gpt-ad-1460505748561-0", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ], - "topframe": 1 - }, - "ext": { - "prebid": { - "bidder": { - "definemedia": {} - } - }, - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" - } - } - ], - "site": { - "domain": "localhost:8080", - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "publisher": { - "id": "1", - "domain": "localhost:8080" - } - }, - "device": { - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "sua": { - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "mobile": 0, - "architecture": "x86", - "bitness": "64", - "source": 2 - }, - "h": 1169, - "w": 1098, - "language": "de" - }, - "tmax": 1000, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1.671449004622e+12, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - }, - "debug": true, - "targeting": { - "includebidderkeys": false, - "includewinners": true - } - } - } - } - }, - "mockResponse": { - "status": 200, - "body": "fail for unmarshall" - } - } - ], - "expectedBidResponses": [], - "expectedMakeBidsErrors": [ - { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", - "comparison": "literal" - } - ] - } \ No newline at end of file diff --git a/adapters/definemedia/definemediatest/supplemental/unsupported-type.json b/adapters/definemedia/definemediatest/supplemental/unsupported-type.json deleted file mode 100644 index 91bfbc413c1..00000000000 --- a/adapters/definemedia/definemediatest/supplemental/unsupported-type.json +++ /dev/null @@ -1,250 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", - "prebid": { - "bidder": { - "definemedia": {} - } - } - }, - "id": "div-gpt-ad-1460505748561-0", - "video": { - "w": 300, - "h": 250, - "maxduration": 60, - "minduration": 1, - "api": [1, 2, 5, 6, 7], - "mimes": ["video/mp4"], - "placement": 4, - "protocols": [2] - } - } - ], - "site": { - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "domain": "localhost:8080", - "publisher": { - "domain": "localhost:8080", - "id": "1" - } - }, - "device": { - "w": 1098, - "h": 1169, - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "language": "de", - "sua": { - "source": 2, - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "mobile": 0, - "model": "", - "bitness": "64", - "architecture": "x86" - } - }, - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "test": 0, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1671449004622, - "targeting": { - "includewinners": true, - "includebidderkeys": false - }, - "debug": true, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - } - } - }, - "tmax": 1000 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://rtb.conative.network/openrtb2/auction", - "body": { - "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", - "imp": [ - { - "id": "div-gpt-ad-1460505748561-0", - "video": { - "w": 300, - "h": 250, - "maxduration": 60, - "minduration": 1, - "api": [1, 2, 5, 6, 7], - "mimes": ["video/mp4"], - "placement": 4, - "protocols": [2] - }, - "ext": { - "prebid": { - "bidder": { - "definemedia": {} - } - }, - "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" - } - } - ], - "site": { - "domain": "localhost:8080", - "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", - "publisher": { - "id": "1", - "domain": "localhost:8080" - } - }, - "device": { - "dnt": 0, - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "sua": { - "browsers": [ - { - "brand": "Not?A_Brand", - "version": [ - "8", - "0", - "0", - "0" - ] - }, - { - "brand": "Chromium", - "version": [ - "108", - "0", - "5359", - "124" - ] - }, - { - "brand": "Google Chrome", - "version": [ - "108", - "0", - "5359", - "124" - ] - } - ], - "platform": { - "brand": "macOS", - "version": [ - "12", - "3", - "1" - ] - }, - "mobile": 0, - "architecture": "x86", - "bitness": "64", - "source": 2 - }, - "h": 1169, - "w": 1098, - "language": "de" - }, - "tmax": 1000, - "source": { - "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" - }, - "ext": { - "prebid": { - "auctiontimestamp": 1.671449004622e+12, - "channel": { - "name": "pbjs", - "version": "v7.28.0" - }, - "debug": true, - "targeting": { - "includebidderkeys": false, - "includewinners": true - } - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "46189656-7e2e-477d-b7f2-e05de224bb89", - "impid": "div-gpt-ad-1460505748561-0", - "price": 100, - "adm": "{banner html}", - "adomain": [ - "test.com" - ], - "crid": "test-creative-id", - "ext": { - "prebid": { - "type": "video" - } - } - } - ] - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [{"currency":"USD","bids":[]}], - "expectedMakeBidsErrors": [ - { - "value": "Invalid mediatype in the impression", - "comparison": "literal" - } - ] -} diff --git a/adapters/definemedia/params_test.go b/adapters/definemedia/params_test.go deleted file mode 100644 index 63ef5272669..00000000000 --- a/adapters/definemedia/params_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package definemedia - -import ( - "encoding/json" - "testing" - - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestValidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json schema. %v", err) - } - - for _, p := range validParams { - if err := validator.Validate(openrtb_ext.BidderDefinemedia, json.RawMessage(p)); err != nil { - t.Errorf("Schema rejected valid params: %s", p) - } - } -} - -func TestInvalidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json schema. %v", err) - } - - for _, p := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderDefinemedia, json.RawMessage(p)); err == nil { - t.Errorf("Schema allowed invalid params: %s", p) - } - } -} - -var validParams = []string{ - `{"mandantId":123}`, - `{"mandantId":123, "adslotId":456}`, -} - -var invalidParams = []string{ - `{"mandantId": "42"}`, - `{"MandantId": "42"}`, - `{"mandantId":123, "adslotId":"456"}`, - `{"adslotId":456}`, - `{"adslotId":"456"}`, - `{}`, -} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 8e80070bd79..7f1b6ea8342 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -71,7 +71,6 @@ import ( "github.com/prebid/prebid-server/adapters/datablocks" "github.com/prebid/prebid-server/adapters/decenterads" "github.com/prebid/prebid-server/adapters/deepintent" - "github.com/prebid/prebid-server/adapters/definemedia" "github.com/prebid/prebid-server/adapters/dianomi" "github.com/prebid/prebid-server/adapters/dmx" evolution "github.com/prebid/prebid-server/adapters/e_volution" @@ -267,7 +266,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderDatablocks: datablocks.Builder, openrtb_ext.BidderDecenterAds: decenterads.Builder, openrtb_ext.BidderDeepintent: deepintent.Builder, - openrtb_ext.BidderDefinemedia: definemedia.Builder, openrtb_ext.BidderDianomi: dianomi.Builder, openrtb_ext.BidderDmx: dmx.Builder, openrtb_ext.BidderEmtv: emtv.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index f6c1ffe8b55..b3fc177b3ac 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -118,6 +118,7 @@ func GetDisabledBidderWarningMessages(infos config.BidderInfos) map[string]strin "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, "brightroll": `Bidder "brightroll" is no longer available in Prebid Server. Please update your configuration.`, + "definemedia": `Bidder "definemedia" is no longer available in Prebid Server. Please update your configuration.`, "applogy": `Bidder "applogy" is no longer available in Prebid Server. Please update your configuration.`, "rhythmone": `Bidder "rhythmone" is no longer available in Prebid Server. Please update your configuration.`, } diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index e3379358012..7dd8093d617 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -91,7 +91,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderDatablocks, BidderDecenterAds, BidderDeepintent, - BidderDefinemedia, BidderDianomi, BidderDmx, BidderEmtv, @@ -385,7 +384,6 @@ const ( BidderDatablocks BidderName = "datablocks" BidderDecenterAds BidderName = "decenterads" BidderDeepintent BidderName = "deepintent" - BidderDefinemedia BidderName = "definemedia" BidderDianomi BidderName = "dianomi" BidderDmx BidderName = "dmx" BidderEmtv BidderName = "emtv" diff --git a/openrtb_ext/imp_definemedia.go b/openrtb_ext/imp_definemedia.go deleted file mode 100644 index aa94bf5de63..00000000000 --- a/openrtb_ext/imp_definemedia.go +++ /dev/null @@ -1,6 +0,0 @@ -package openrtb_ext - -type ImpExtDefinemedia struct { - MandantID int64 `json:"mandantId"` - AdslotID int64 `json:"adslotId"` -} diff --git a/static/bidder-info/definemedia.yaml b/static/bidder-info/definemedia.yaml deleted file mode 100644 index d79c3835a58..00000000000 --- a/static/bidder-info/definemedia.yaml +++ /dev/null @@ -1,10 +0,0 @@ -endpoint: "https://rtb.conative.network/openrtb2/auction" -maintainer: - email: "d.joest@definemedia.de" - gvlVendorID: 440 # GDPR vendor list ID -capabilities: - - site: - mediaTypes: - - banner - - native diff --git a/static/bidder-params/definemedia.json b/static/bidder-params/definemedia.json deleted file mode 100644 index bf5566b4c89..00000000000 --- a/static/bidder-params/definemedia.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Define Media Adapter Params", - "description": "A schema which validates params accepted by the DM adapter", - "type": "object", - - "properties": { - "mandantId": { - "type": "integer", - "description": "The DEFINE-MEDIA mandant id. This is a unique identifier for your account. Please contact your account manager for more information." - }, - - "adslotId":{ - "type": "integer", - "description": "The adslot id. This is a unique identifier for your adslot and may change on subparts on a website. Please contact your account manager for more information." - } - }, - "required": ["mandantId"] - } \ No newline at end of file From 10d854cd3fcae8cd3b41cd59ae2f84890b8a4805 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 26 Sep 2023 05:30:30 -0400 Subject: [PATCH 015/138] Remove Adapter: Kubient (#3133) Co-authored-by: Ashish Garg --- adapters/kubient/kubient.go | 149 ------------------ adapters/kubient/kubient_test.go | 20 --- .../kubient/kubienttest/exemplary/banner.json | 103 ------------ .../kubient/kubienttest/exemplary/video.json | 93 ----------- .../supplemental/bad_response.json | 61 ------- .../supplemental/missing-zoneid.json | 31 ---- .../kubienttest/supplemental/no-imps.json | 12 -- .../kubienttest/supplemental/status_204.json | 58 ------- .../kubienttest/supplemental/status_400.json | 63 -------- exchange/adapter_builders.go | 2 - exchange/adapter_util.go | 1 + openrtb_ext/bidders.go | 2 - openrtb_ext/imp_kubient.go | 6 - static/bidder-info/kubient.yaml | 13 -- static/bidder-params/kubient.json | 13 -- 15 files changed, 1 insertion(+), 626 deletions(-) delete mode 100644 adapters/kubient/kubient.go delete mode 100644 adapters/kubient/kubient_test.go delete mode 100644 adapters/kubient/kubienttest/exemplary/banner.json delete mode 100644 adapters/kubient/kubienttest/exemplary/video.json delete mode 100644 adapters/kubient/kubienttest/supplemental/bad_response.json delete mode 100644 adapters/kubient/kubienttest/supplemental/missing-zoneid.json delete mode 100644 adapters/kubient/kubienttest/supplemental/no-imps.json delete mode 100644 adapters/kubient/kubienttest/supplemental/status_204.json delete mode 100644 adapters/kubient/kubienttest/supplemental/status_400.json delete mode 100644 openrtb_ext/imp_kubient.go delete mode 100644 static/bidder-info/kubient.yaml delete mode 100644 static/bidder-params/kubient.json diff --git a/adapters/kubient/kubient.go b/adapters/kubient/kubient.go deleted file mode 100644 index 8a3a7baa65a..00000000000 --- a/adapters/kubient/kubient.go +++ /dev/null @@ -1,149 +0,0 @@ -package kubient - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" -) - -// Builder builds a new instance of the Kubient adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &KubientAdapter{ - endpoint: config.Endpoint, - } - return bidder, nil -} - -// Implements Bidder interface. -type KubientAdapter struct { - endpoint string -} - -// MakeRequests prepares the HTTP requests which should be made to fetch bids. -func (adapter *KubientAdapter) MakeRequests( - openRTBRequest *openrtb2.BidRequest, - reqInfo *adapters.ExtraRequestInfo, -) ([]*adapters.RequestData, []error) { - if len(openRTBRequest.Imp) == 0 { - return nil, []error{&errortypes.BadInput{ - Message: "No impression in the bid request", - }} - } - errs := make([]error, 0, len(openRTBRequest.Imp)) - hasErrors := false - for _, impObj := range openRTBRequest.Imp { - err := checkImpExt(impObj) - if err != nil { - errs = append(errs, err) - hasErrors = true - } - } - if hasErrors { - return nil, errs - } - openRTBRequestJSON, err := json.Marshal(openRTBRequest) - if err != nil { - errs = append(errs, err) - return nil, errs - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - requestsToBidder := []*adapters.RequestData{{ - Method: "POST", - Uri: adapter.endpoint, - Body: openRTBRequestJSON, - Headers: headers, - }} - return requestsToBidder, errs -} - -func checkImpExt(impObj openrtb2.Imp) error { - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(impObj.Ext, &bidderExt); err != nil { - return &errortypes.BadInput{ - Message: "ext.bidder not provided", - } - } - var kubientExt openrtb_ext.ExtImpKubient - if err := json.Unmarshal(bidderExt.Bidder, &kubientExt); err != nil { - return &errortypes.BadInput{ - Message: "ext.bidder.zoneid is not provided", - } - } - if kubientExt.ZoneID == "" { - return &errortypes.BadInput{ - Message: "zoneid is empty", - } - } - return nil -} - -// MakeBids makes the bids -func (adapter *KubientAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - var errs []error - - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - var bidResp openrtb2.BidResponse - - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{err} - } - - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) - if err != nil { - errs = append(errs, err) - } else { - b := &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: bidType, - } - bidResponse.Bids = append(bidResponse.Bids, b) - } - } - } - return bidResponse, errs -} - -func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { - mediaType := openrtb_ext.BidTypeBanner - for _, imp := range imps { - if imp.ID == impID { - if imp.Banner == nil && imp.Video != nil { - mediaType = openrtb_ext.BidTypeVideo - } - return mediaType, nil - } - } - - // This shouldnt happen. Lets handle it just incase by returning an error. - return "", &errortypes.BadInput{ - Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), - } -} diff --git a/adapters/kubient/kubient_test.go b/adapters/kubient/kubient_test.go deleted file mode 100644 index 292bb20641a..00000000000 --- a/adapters/kubient/kubient_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package kubient - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderKubient, config.Adapter{ - Endpoint: "http://127.0.0.1:5000/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "kubienttest", bidder) -} diff --git a/adapters/kubient/kubienttest/exemplary/banner.json b/adapters/kubient/kubienttest/exemplary/banner.json deleted file mode 100644 index abcfc05c041..00000000000 --- a/adapters/kubient/kubienttest/exemplary/banner.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-banner-request-id", - "imp": [ - { - "id": "test-imp-banner-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "zoneid": "9042" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://127.0.0.1:5000/bid", - "body": { - "id": "test-banner-request-id", - "imp": [ - { - "id": "test-imp-banner-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "zoneid": "9042" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-banner-request-id", - "seatbid": [ - { - "seat": "772", - "bid": [{ - "id": "7706636740145184841", - "impid": "test-imp-banner-id", - "price": 0.500000, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": ["advertsite.com"], - "cid": "772", - "crid": "29681110", - "h": 576, - "w": 1024 - }] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-banner-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": ["advertsite.com"], - "cid": "772", - "crid": "29681110", - "w": 1024, - "h": 576 - }, - "type": "banner" - }] - } - ] -} diff --git a/adapters/kubient/kubienttest/exemplary/video.json b/adapters/kubient/kubienttest/exemplary/video.json deleted file mode 100644 index 69de5935f48..00000000000 --- a/adapters/kubient/kubienttest/exemplary/video.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-video-request-id", - "imp": [ - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1024, - "h": 576 - }, - "ext": { - "bidder": { - "zoneid": "9010" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://127.0.0.1:5000/bid", - "body": { - "id": "test-video-request-id", - "imp": [ - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1024, - "h": 576 - }, - "ext": { - "bidder": { - "zoneid": "9010" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "ssp-response-id", - "seatbid": [ - { - "seat": "83", - "bid": [{ - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.500000, - "adid": "29681110", - "adm": "some-video-ad", - "adomain": ["advertsite.com"], - "cid": "958", - "crid": "29681110", - "h": 576, - "w": 1024 - }] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adm": "some-video-ad", - "adid": "29681110", - "adomain": ["advertsite.com"], - "cid": "958", - "crid": "29681110", - "w": 1024, - "h": 576 - }, - "type": "video" - }] - } - ] - -} diff --git a/adapters/kubient/kubienttest/supplemental/bad_response.json b/adapters/kubient/kubienttest/supplemental/bad_response.json deleted file mode 100644 index 832dc975088..00000000000 --- a/adapters/kubient/kubienttest/supplemental/bad_response.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": "23" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://127.0.0.1:5000/bid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": "23" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": "{\"id\"data.lost" - } - } - ], - "expectedMakeBidsErrors": [ - { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", - "comparison": "literal" - } - ] -} diff --git a/adapters/kubient/kubienttest/supplemental/missing-zoneid.json b/adapters/kubient/kubienttest/supplemental/missing-zoneid.json deleted file mode 100644 index cfd616621e2..00000000000 --- a/adapters/kubient/kubienttest/supplemental/missing-zoneid.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-missing-req-param-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": {} - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "zoneid is empty", - "comparison": "literal" - } - ] -} diff --git a/adapters/kubient/kubienttest/supplemental/no-imps.json b/adapters/kubient/kubienttest/supplemental/no-imps.json deleted file mode 100644 index 189adf9a932..00000000000 --- a/adapters/kubient/kubienttest/supplemental/no-imps.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-no-imp-request-id", - "imp": [] - }, - "expectedMakeRequestsErrors": [ - { - "value": "No impression in the bid request", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/kubient/kubienttest/supplemental/status_204.json b/adapters/kubient/kubienttest/supplemental/status_204.json deleted file mode 100644 index 6794d58be6c..00000000000 --- a/adapters/kubient/kubienttest/supplemental/status_204.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": "203" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://127.0.0.1:5000/bid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": "203" - } - } - } - ] - } - }, - "mockResponse": { - "status": 204, - "body": {} - } - } - ], - - "expectedBidResponses": [] -} diff --git a/adapters/kubient/kubienttest/supplemental/status_400.json b/adapters/kubient/kubienttest/supplemental/status_400.json deleted file mode 100644 index 29438cc3b8b..00000000000 --- a/adapters/kubient/kubienttest/supplemental/status_400.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": "102" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://127.0.0.1:5000/bid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": "102" - } - } - } - ] - } - }, - "mockResponse": { - "status": 400, - "body": {} - } - } - ], - - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] -} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 7f1b6ea8342..122cc6f25a9 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -103,7 +103,6 @@ import ( "github.com/prebid/prebid-server/adapters/kidoz" "github.com/prebid/prebid-server/adapters/kiviads" "github.com/prebid/prebid-server/adapters/krushmedia" - "github.com/prebid/prebid-server/adapters/kubient" "github.com/prebid/prebid-server/adapters/liftoff" "github.com/prebid/prebid-server/adapters/limelightDigital" lmkiviads "github.com/prebid/prebid-server/adapters/lm_kiviads" @@ -306,7 +305,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderKiviads: kiviads.Builder, openrtb_ext.BidderLmKiviads: lmkiviads.Builder, openrtb_ext.BidderKrushmedia: krushmedia.Builder, - openrtb_ext.BidderKubient: kubient.Builder, openrtb_ext.BidderLiftoff: liftoff.Builder, openrtb_ext.BidderLimelightDigital: limelightDigital.Builder, openrtb_ext.BidderLockerDome: lockerdome.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index b3fc177b3ac..5464d328eed 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -118,6 +118,7 @@ func GetDisabledBidderWarningMessages(infos config.BidderInfos) map[string]strin "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, "brightroll": `Bidder "brightroll" is no longer available in Prebid Server. Please update your configuration.`, + "kubient": `Bidder "kubient" is no longer available in Prebid Server. Please update your configuration.`, "definemedia": `Bidder "definemedia" is no longer available in Prebid Server. Please update your configuration.`, "applogy": `Bidder "applogy" is no longer available in Prebid Server. Please update your configuration.`, "rhythmone": `Bidder "rhythmone" is no longer available in Prebid Server. Please update your configuration.`, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 7dd8093d617..c60ea13e81f 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -131,7 +131,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderKiviads, BidderLmKiviads, BidderKrushmedia, - BidderKubient, BidderLiftoff, BidderLimelightDigital, BidderLockerDome, @@ -424,7 +423,6 @@ const ( BidderKiviads BidderName = "kiviads" BidderLmKiviads BidderName = "lm_kiviads" BidderKrushmedia BidderName = "krushmedia" - BidderKubient BidderName = "kubient" BidderLiftoff BidderName = "liftoff" BidderLimelightDigital BidderName = "limelightDigital" BidderLockerDome BidderName = "lockerdome" diff --git a/openrtb_ext/imp_kubient.go b/openrtb_ext/imp_kubient.go deleted file mode 100644 index 59dd3d2aaab..00000000000 --- a/openrtb_ext/imp_kubient.go +++ /dev/null @@ -1,6 +0,0 @@ -package openrtb_ext - -// ExtImpKubient defines the contract for bidrequest.imp[i].ext.prebid.bidder.kubient -type ExtImpKubient struct { - ZoneID string `json:"zoneid"` -} diff --git a/static/bidder-info/kubient.yaml b/static/bidder-info/kubient.yaml deleted file mode 100644 index 15c2708bcb3..00000000000 --- a/static/bidder-info/kubient.yaml +++ /dev/null @@ -1,13 +0,0 @@ -endpoint: "https://kssp.kbntx.ch/prebid" -maintainer: - email: "prebid@kubient.com" -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video - diff --git a/static/bidder-params/kubient.json b/static/bidder-params/kubient.json deleted file mode 100644 index 9b975289a7b..00000000000 --- a/static/bidder-params/kubient.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Kubient Adapter Params", - "description": "A schema which validates params accepted by the Kubient adapter", - "type": "object", - "properties": { - "zoneid": { - "type": "string", - "description": "Zone ID identifies Kubient placement ID.", - "minLength": 1 - } - } -} From 03779e3624f443d90bf987307239d02044f1646c Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 26 Sep 2023 06:57:10 -0400 Subject: [PATCH 016/138] Remove Adapter: NinthDecimal (#3142) Co-authored-by: Ashish Garg --- adapters/ninthdecimal/ninthdecimal.go | 241 ------------------ adapters/ninthdecimal/ninthdecimal_test.go | 28 -- .../ninthdecimaltest/exemplary/banner.json | 95 ------- .../ninthdecimaltest/exemplary/video.json | 83 ------ .../supplemental/checkImp.json | 14 - .../ninthdecimaltest/supplemental/compat.json | 80 ------ .../ninthdecimaltest/supplemental/ext.json | 33 --- .../supplemental/missingpub.json | 35 --- .../supplemental/responseCode.json | 78 ------ .../supplemental/responsebid.json | 79 ------ .../ninthdecimaltest/supplemental/site.json | 103 -------- .../ninthdecimaltest/supplemental/size.json | 28 -- adapters/ninthdecimal/params_test.go | 45 ---- exchange/adapter_builders.go | 2 - exchange/adapter_util.go | 1 + openrtb_ext/bidders.go | 2 - openrtb_ext/imp_ninthdecimal.go | 6 - static/bidder-info/ninthdecimal.yaml | 18 -- static/bidder-params/ninthdecimal.json | 18 -- 19 files changed, 1 insertion(+), 988 deletions(-) delete mode 100755 adapters/ninthdecimal/ninthdecimal.go delete mode 100755 adapters/ninthdecimal/ninthdecimal_test.go delete mode 100644 adapters/ninthdecimal/ninthdecimaltest/exemplary/banner.json delete mode 100644 adapters/ninthdecimal/ninthdecimaltest/exemplary/video.json delete mode 100644 adapters/ninthdecimal/ninthdecimaltest/supplemental/checkImp.json delete mode 100644 adapters/ninthdecimal/ninthdecimaltest/supplemental/compat.json delete mode 100644 adapters/ninthdecimal/ninthdecimaltest/supplemental/ext.json delete mode 100644 adapters/ninthdecimal/ninthdecimaltest/supplemental/missingpub.json delete mode 100644 adapters/ninthdecimal/ninthdecimaltest/supplemental/responseCode.json delete mode 100644 adapters/ninthdecimal/ninthdecimaltest/supplemental/responsebid.json delete mode 100644 adapters/ninthdecimal/ninthdecimaltest/supplemental/site.json delete mode 100644 adapters/ninthdecimal/ninthdecimaltest/supplemental/size.json delete mode 100755 adapters/ninthdecimal/params_test.go delete mode 100755 openrtb_ext/imp_ninthdecimal.go delete mode 100755 static/bidder-info/ninthdecimal.yaml delete mode 100755 static/bidder-params/ninthdecimal.json diff --git a/adapters/ninthdecimal/ninthdecimal.go b/adapters/ninthdecimal/ninthdecimal.go deleted file mode 100755 index 70dd60ab9fe..00000000000 --- a/adapters/ninthdecimal/ninthdecimal.go +++ /dev/null @@ -1,241 +0,0 @@ -package ninthdecimal - -import ( - "encoding/json" - "fmt" - "net/http" - "text/template" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" -) - -type NinthDecimalAdapter struct { - EndpointTemplate *template.Template -} - -// MakeRequests prepares request information for prebid-server core -func (adapter *NinthDecimalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - errs := make([]error, 0, len(request.Imp)) - if len(request.Imp) == 0 { - errs = append(errs, &errortypes.BadInput{Message: "No impression in the bid request"}) - return nil, errs - } - pub2impressions, imps, err := getImpressionsInfo(request.Imp) - if len(imps) == 0 { - return nil, err - } - errs = append(errs, err...) - - if len(pub2impressions) == 0 { - return nil, errs - } - - result := make([]*adapters.RequestData, 0, len(pub2impressions)) - for k, imps := range pub2impressions { - bidRequest, err := adapter.buildAdapterRequest(request, &k, imps) - if err != nil { - errs = append(errs, err) - return nil, errs - } else { - result = append(result, bidRequest) - } - } - return result, errs -} - -// getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts -func getImpressionsInfo(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpNinthDecimal][]openrtb2.Imp, []openrtb2.Imp, []error) { - errors := make([]error, 0, len(imps)) - resImps := make([]openrtb2.Imp, 0, len(imps)) - res := make(map[openrtb_ext.ExtImpNinthDecimal][]openrtb2.Imp) - - for _, imp := range imps { - impExt, err := getImpressionExt(&imp) - if err != nil { - errors = append(errors, err) - continue - } - if err := validateImpression(impExt); err != nil { - errors = append(errors, err) - continue - } - //dispatchImpressions - //Group impressions by NinthDecimal-specific parameters `pubid - if err := compatImpression(&imp); err != nil { - errors = append(errors, err) - continue - } - if res[*impExt] == nil { - res[*impExt] = make([]openrtb2.Imp, 0) - } - res[*impExt] = append(res[*impExt], imp) - resImps = append(resImps, imp) - } - return res, resImps, errors -} - -func validateImpression(impExt *openrtb_ext.ExtImpNinthDecimal) error { - if impExt.PublisherID == "" { - return &errortypes.BadInput{Message: "No pubid value provided"} - } - return nil -} - -// Alter impression info to comply with NinthDecimal platform requirements -func compatImpression(imp *openrtb2.Imp) error { - imp.Ext = nil //do not forward ext to NinthDecimal platform - if imp.Banner != nil { - return compatBannerImpression(imp) - } - return nil -} - -func compatBannerImpression(imp *openrtb2.Imp) error { - // Create a copy of the banner, since imp is a shallow copy of the original. - - bannerCopy := *imp.Banner - banner := &bannerCopy - //As banner.w/h are required fields for NinthDecimal platform - take the first format entry - if banner.W == nil || banner.H == nil { - if len(banner.Format) == 0 { - return &errortypes.BadInput{Message: "Expected at least one banner.format entry or explicit w/h"} - } - format := banner.Format[0] - banner.Format = banner.Format[1:] - banner.W = &format.W - banner.H = &format.H - imp.Banner = banner - } - return nil -} - -func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpNinthDecimal, error) { - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - var NinthDecimalExt openrtb_ext.ExtImpNinthDecimal - if err := json.Unmarshal(bidderExt.Bidder, &NinthDecimalExt); err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - return &NinthDecimalExt, nil -} - -func (adapter *NinthDecimalAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb2.Imp) (*adapters.RequestData, error) { - newBidRequest := createBidRequest(prebidBidRequest, params, imps) - reqJSON, err := json.Marshal(newBidRequest) - if err != nil { - return nil, err - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - headers.Add("x-openrtb-version", "2.5") - - url, err := adapter.buildEndpointURL(params) - if err != nil { - return nil, err - } - - return &adapters.RequestData{ - Method: "POST", - Uri: url, - Body: reqJSON, - Headers: headers}, nil -} - -func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb2.Imp) *openrtb2.BidRequest { - bidRequest := *prebidBidRequest - bidRequest.Imp = imps - for idx := range bidRequest.Imp { - imp := &bidRequest.Imp[idx] - imp.TagID = params.Placement - } - if bidRequest.Site != nil { - // Need to copy Site as Request is a shallow copy - siteCopy := *bidRequest.Site - bidRequest.Site = &siteCopy - bidRequest.Site.Publisher = nil - bidRequest.Site.Domain = "" - } - if bidRequest.App != nil { - // Need to copy App as Request is a shallow copy - appCopy := *bidRequest.App - bidRequest.App = &appCopy - bidRequest.App.Publisher = nil - } - return &bidRequest -} - -// Builds enpoint url based on adapter-specific pub settings from imp.ext -func (adapter *NinthDecimalAdapter) buildEndpointURL(params *openrtb_ext.ExtImpNinthDecimal) (string, error) { - endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherID} - return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) -} - -// MakeBids translates NinthDecimal bid response to prebid-server specific format -func (adapter *NinthDecimalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - var msg = "" - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - if response.StatusCode != http.StatusOK { - msg = fmt.Sprintf("Unexpected http status code: %d", response.StatusCode) - return nil, []error{&errortypes.BadServerResponse{Message: msg}} - - } - var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - msg = fmt.Sprintf("Bad server response: %d", err) - return nil, []error{&errortypes.BadServerResponse{Message: msg}} - } - if len(bidResp.SeatBid) != 1 { - var msg = fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid)) - return nil, []error{&errortypes.BadServerResponse{Message: msg}} - } - - seatBid := bidResp.SeatBid[0] - bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) - - for i := 0; i < len(seatBid.Bid); i++ { - bid := seatBid.Bid[i] - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: getMediaTypeForImpID(bid.ImpID, internalRequest.Imp), - }) - } - return bidResponse, nil -} - -// getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { - for _, imp := range imps { - if imp.ID == impID && imp.Video != nil { - return openrtb_ext.BidTypeVideo - } - } - return openrtb_ext.BidTypeBanner -} - -// Builder builds a new instance of the NinthDecimal adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - template, err := template.New("endpointTemplate").Parse(config.Endpoint) - if err != nil { - return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) - } - - bidder := &NinthDecimalAdapter{ - EndpointTemplate: template, - } - return bidder, nil -} diff --git a/adapters/ninthdecimal/ninthdecimal_test.go b/adapters/ninthdecimal/ninthdecimal_test.go deleted file mode 100755 index 8932ac22f58..00000000000 --- a/adapters/ninthdecimal/ninthdecimal_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package ninthdecimal - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderNinthDecimal, config.Adapter{ - Endpoint: "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "ninthdecimaltest", bidder) -} - -func TestEndpointTemplateMalformed(t *testing.T) { - _, buildErr := Builder(openrtb_ext.BidderNinthDecimal, config.Adapter{ - Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - assert.Error(t, buildErr) -} diff --git a/adapters/ninthdecimal/ninthdecimaltest/exemplary/banner.json b/adapters/ninthdecimal/ninthdecimaltest/exemplary/banner.json deleted file mode 100644 index d2184fa06b6..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/exemplary/banner.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [ - { - "w": 320, - "h": 250 - }, - { - "w": 320, - "h": 300 - } - ], - "w": 320, - "h": 250 - }, - "ext": { - "bidder": { - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", - "body":{ - "id": "testid", - "imp": [{ - "id": "testimpid", - "tagid": "dummyplacement", - "banner": { - "format": [{ - "w": 320, - "h": 250 - }, { - "w": 320, - "h": 300 - }], - "w": 320, - "h": 250 - } - - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "seatbid": [ - { - "bid": [ - { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - } - ] - } - ] - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/ninthdecimal/ninthdecimaltest/exemplary/video.json b/adapters/ninthdecimal/ninthdecimaltest/exemplary/video.json deleted file mode 100644 index 4ad093e0648..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/exemplary/video.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "imp": [ - { - "id": "testimpid", - "video": { - "mimes": [ - "video/mp4" - ], - "w": 640, - "h": 480 - }, - "ext": { - "bidder": { - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", - "body":{ - "id": "testid", - "imp": [{ - "id": "testimpid", - "tagid": "dummyplacement", - "video": { - "mimes": [ - "video/mp4" - ], - "w": 640, - "h": 480 - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "seatbid": [ - { - "bid": [ - { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - } - ] - } - ] - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - }, - "type": "video" - } - ] - } - ] -} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/checkImp.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/checkImp.json deleted file mode 100644 index ca48812b4df..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/checkImp.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "site": { - "id": "test", - "domain": "test.com" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "No impression in the bid request", - "comparison": "literal" - }] - } \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/compat.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/compat.json deleted file mode 100644 index ff33b59cff9..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/compat.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [{ - "w": 320, - "h": 250 - }] - }, - "ext": { - "bidder": { - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", - "body":{ - "id": "testid", - "imp": [{ - "banner": { - "h": 250, - "w": 320 - }, - "id": "testimpid", - "tagid": "dummyplacement" - - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "seatbid": [ - { - "bid": [ - { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - } - ] - } - ] - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/ext.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/ext.json deleted file mode 100644 index 3cfb878bd47..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/ext.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [ - { - "w": 320, - "h": 250 - }, - { - "w": 320, - "h": 300 - } - ], - "w": 320, - "h": 250 - }, - "ext": { - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" - } - } - ] - }, -"expectedMakeRequestsErrors": [ - { - "value": "unexpected end of JSON input", - "comparison": "literal" - }] -} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/missingpub.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/missingpub.json deleted file mode 100644 index b088917afa3..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/missingpub.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [ - { - "w": 320, - "h": 250 - }, - { - "w": 320, - "h": 300 - } - ], - "w": 320, - "h": 250 - }, - "ext": { - "bidder": { - "pubid": "", - "placement": "dummyplacement" - } - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "No pubid value provided", - "comparison": "literal" - }] - } \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/responseCode.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/responseCode.json deleted file mode 100644 index a68db2b823e..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/responseCode.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "site": { - "id": "test" - }, - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [ - { - "w": 320, - "h": 250 - }, - { - "w": 320, - "h": 300 - } - ], - "w": 320, - "h": 250 - }, - "ext": { - "bidder": { - "pubid": "yu", - "placement": "dummyplacement" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=yu", - "body": { - "id": "testid", - "imp": [ - { - "banner": { - "format": [ - { - "h": 250, - "w": 320 - }, - { - "h": 300, - "w": 320 - } - ], - "h": 250, - "w": 320 - }, - "id": "testimpid", - "tagid": "dummyplacement" - } - ], - "site": { - "id": "test" - } - } - }, - "mockResponse": { - "body": { - "seatbid": [] - } - } - } - - ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected http status code: 0", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/responsebid.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/responsebid.json deleted file mode 100644 index c03aa1b2d89..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/responsebid.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "site": { - "id": "test" - }, - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [ - { - "w": 320, - "h": 250 - }, - { - "w": 320, - "h": 300 - } - ], - "w": 320, - "h": 250 - }, - "ext": { - "bidder": { - "pubid": "yu", - "placement": "dummyplacement" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=yu", - "body": { - "id": "testid", - "imp": [ - { - "banner": { - "format": [ - { - "h": 250, - "w": 320 - }, - { - "h": 300, - "w": 320 - } - ], - "h": 250, - "w": 320 - }, - "id": "testimpid", - "tagid": "dummyplacement" - } - ], - "site": { - "id": "test" - } - } - }, - "mockResponse": { - "status":200, - "body": { - "seatbid": [] - } - } - } - - ], - "expectedMakeBidsErrors": [ - { - "value": "Invalid SeatBids count: 0", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/site.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/site.json deleted file mode 100644 index a3e160e4bd8..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/site.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "site": { - "id": "test" - }, - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [ - { - "w": 320, - "h": 250 - }, - { - "w": 320, - "h": 300 - } - ], - "w": 320, - "h": 250 - }, - "ext": { - "bidder": { - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", - "body": { - "id": "testid", - "imp": [ - { - "banner": { - "format": [ - { - "h": 250, - "w": 320 - }, - { - "h": 300, - "w": 320 - } - ], - "h": 250, - "w": 320 - }, - "id": "testimpid", - "tagid": "dummyplacement" - } - ], - "site": { - "id": "test" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "seatbid": [ - { - "bid": [ - { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - }, - "type": "banner" - } - ] - } - ] -} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/size.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/size.json deleted file mode 100644 index 77228559eee..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/size.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "site": { - "id": "test", - "domain": "test.com" - }, - "imp": [ - { - "id": "testimpid", - "banner": { - - }, - "ext": { - "bidder": { - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" - } - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "Expected at least one banner.format entry or explicit w/h", - "comparison": "literal" - }] -} \ No newline at end of file diff --git a/adapters/ninthdecimal/params_test.go b/adapters/ninthdecimal/params_test.go deleted file mode 100755 index 8d3ef3d706f..00000000000 --- a/adapters/ninthdecimal/params_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package ninthdecimal - -import ( - "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" - "testing" -) - -func TestValidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderNinthDecimal, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected NinthDecimal params: %s", validParam) - } - } -} - -func TestInvalidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderNinthDecimal, json.RawMessage(invalidParam)); err == nil { - t.Errorf("Schema allowed unexpected params: %s", invalidParam) - } - } -} - -var validParams = []string{ - `{"pubid": "19f1b372c7548ec1fe734d2c9f8dc688"}`, -} - -var invalidParams = []string{ - `{"publisher": "19f1b372c7548ec1fe734d2c9f8dc688"}`, - `nil`, - ``, - `[]`, - `true`, -} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 122cc6f25a9..527e4c87b4e 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -121,7 +121,6 @@ import ( "github.com/prebid/prebid-server/adapters/motorik" "github.com/prebid/prebid-server/adapters/nanointeractive" "github.com/prebid/prebid-server/adapters/nextmillennium" - "github.com/prebid/prebid-server/adapters/ninthdecimal" "github.com/prebid/prebid-server/adapters/nobid" "github.com/prebid/prebid-server/adapters/onetag" "github.com/prebid/prebid-server/adapters/openweb" @@ -323,7 +322,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderMotorik: motorik.Builder, openrtb_ext.BidderNanoInteractive: nanointeractive.Builder, openrtb_ext.BidderNextMillennium: nextmillennium.Builder, - openrtb_ext.BidderNinthDecimal: ninthdecimal.Builder, openrtb_ext.BidderNoBid: nobid.Builder, openrtb_ext.BidderOneTag: onetag.Builder, openrtb_ext.BidderOpenWeb: openweb.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 5464d328eed..11300d479d2 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -118,6 +118,7 @@ func GetDisabledBidderWarningMessages(infos config.BidderInfos) map[string]strin "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, "brightroll": `Bidder "brightroll" is no longer available in Prebid Server. Please update your configuration.`, + "ninthdecimal": `Bidder "ninthdecimal" is no longer available in Prebid Server. Please update your configuration.`, "kubient": `Bidder "kubient" is no longer available in Prebid Server. Please update your configuration.`, "definemedia": `Bidder "definemedia" is no longer available in Prebid Server. Please update your configuration.`, "applogy": `Bidder "applogy" is no longer available in Prebid Server. Please update your configuration.`, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index c60ea13e81f..049ce11d462 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -149,7 +149,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderMotorik, BidderNanoInteractive, BidderNextMillennium, - BidderNinthDecimal, BidderNoBid, BidderOneTag, BidderOpenWeb, @@ -441,7 +440,6 @@ const ( BidderMotorik BidderName = "motorik" BidderNanoInteractive BidderName = "nanointeractive" BidderNextMillennium BidderName = "nextmillennium" - BidderNinthDecimal BidderName = "ninthdecimal" BidderNoBid BidderName = "nobid" BidderOneTag BidderName = "onetag" BidderOpenWeb BidderName = "openweb" diff --git a/openrtb_ext/imp_ninthdecimal.go b/openrtb_ext/imp_ninthdecimal.go deleted file mode 100755 index 8fb794dbdf2..00000000000 --- a/openrtb_ext/imp_ninthdecimal.go +++ /dev/null @@ -1,6 +0,0 @@ -package openrtb_ext - -type ExtImpNinthDecimal struct { - PublisherID string `json:"pubid"` - Placement string `json:"placement,omitempty"` -} diff --git a/static/bidder-info/ninthdecimal.yaml b/static/bidder-info/ninthdecimal.yaml deleted file mode 100755 index 8a4d7b8d299..00000000000 --- a/static/bidder-info/ninthdecimal.yaml +++ /dev/null @@ -1,18 +0,0 @@ -endpoint: "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}" -maintainer: - email: "abudig@ninthdecimal.com" -capabilities: - site: - mediaTypes: - - banner - - video - - app: - mediaTypes: - - banner - - video -userSync: - iframe: - url: "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect={{.RedirectURL}}" - userMacro: "$UID" - diff --git a/static/bidder-params/ninthdecimal.json b/static/bidder-params/ninthdecimal.json deleted file mode 100755 index f230361d77e..00000000000 --- a/static/bidder-params/ninthdecimal.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "NinthDecimal Adapter Params", - "description": "A schema which validates params accepted by the NinthDecimal adapter", - "type": "object", - "properties": { - "pubid": { - "type": "string", - "description": "An id used to identify NinthDecimal publisher.", - "minLength": 8 - }, - "placement": { - "type": "string", - "description": "A placement created on adserver." - } - }, - "required": ["pubid"] -} From e3cf3c38b8efd82ac7efe59e137179713c61b804 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 26 Sep 2023 07:10:35 -0400 Subject: [PATCH 017/138] Remove Adapter: Yeahmobi (#3143) Co-authored-by: Ashish Garg --- adapters/yeahmobi/params_test.go | 47 ----- adapters/yeahmobi/yeahmobi.go | 189 ------------------ adapters/yeahmobi/yeahmobi_test.go | 28 --- .../yeahmobitest/exemplary/no-bid.json | 51 ----- .../yeahmobitest/exemplary/simple-banner.json | 81 -------- .../exemplary/simple-native-1.1.json | 82 -------- .../yeahmobitest/exemplary/simple-native.json | 82 -------- .../yeahmobitest/exemplary/simple-video.json | 89 --------- .../supplemental/bad_imp_ext.json | 21 -- .../supplemental/bad_imp_ext_bidder.json | 23 --- .../supplemental/bad_response.json | 55 ----- .../yeahmobitest/supplemental/status_400.json | 55 ----- .../yeahmobitest/supplemental/status_500.json | 55 ----- exchange/adapter_builders.go | 2 - exchange/adapter_util.go | 1 + openrtb_ext/bidders.go | 2 - openrtb_ext/imp_yeahmobi.go | 7 - static/bidder-info/yeahmobi.yaml | 9 - static/bidder-params/yeahmobi.json | 21 -- 19 files changed, 1 insertion(+), 899 deletions(-) delete mode 100644 adapters/yeahmobi/params_test.go delete mode 100644 adapters/yeahmobi/yeahmobi.go delete mode 100644 adapters/yeahmobi/yeahmobi_test.go delete mode 100644 adapters/yeahmobi/yeahmobitest/exemplary/no-bid.json delete mode 100644 adapters/yeahmobi/yeahmobitest/exemplary/simple-banner.json delete mode 100644 adapters/yeahmobi/yeahmobitest/exemplary/simple-native-1.1.json delete mode 100644 adapters/yeahmobi/yeahmobitest/exemplary/simple-native.json delete mode 100644 adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json delete mode 100644 adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext.json delete mode 100644 adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext_bidder.json delete mode 100644 adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json delete mode 100644 adapters/yeahmobi/yeahmobitest/supplemental/status_400.json delete mode 100644 adapters/yeahmobi/yeahmobitest/supplemental/status_500.json delete mode 100644 openrtb_ext/imp_yeahmobi.go delete mode 100644 static/bidder-info/yeahmobi.yaml delete mode 100644 static/bidder-params/yeahmobi.json diff --git a/adapters/yeahmobi/params_test.go b/adapters/yeahmobi/params_test.go deleted file mode 100644 index 997bf93a53f..00000000000 --- a/adapters/yeahmobi/params_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package yeahmobi - -import ( - "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" - "testing" -) - -func TestValidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderYeahmobi, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected yeahmobi params: %s", validParam) - } - } -} - -// TestInvalidParams makes sure that the yeahmobi schema rejects all the imp.ext fields we don't support. -func TestInvalidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderYeahmobi, json.RawMessage(invalidParam)); err == nil { - t.Errorf("Schema allowed unexpected params: %s", invalidParam) - } - } -} - -var validParams = []string{ - `{"pubId": "11233", "zoneId": "sin"}`, - `{"pubId": "11244", "zoneId": "iad"}`, -} - -var invalidParams = []string{ - `{"pubId": "11233"}`, - `{"zoneId": "aaa"}`, - `{"pubId": 123, "zoneId": "sin"}`, - `{"pubId": "", "zoneId": "iad"}`, - `{"pubId": "11233", "zoneId": ""}`, -} diff --git a/adapters/yeahmobi/yeahmobi.go b/adapters/yeahmobi/yeahmobi.go deleted file mode 100644 index d25b2eab541..00000000000 --- a/adapters/yeahmobi/yeahmobi.go +++ /dev/null @@ -1,189 +0,0 @@ -package yeahmobi - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - "text/template" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" -) - -type YeahmobiAdapter struct { - EndpointTemplate *template.Template -} - -// Builder builds a new instance of the Yeahmobi adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - template, err := template.New("endpointTemplate").Parse(config.Endpoint) - if err != nil { - return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) - } - - bidder := &YeahmobiAdapter{ - EndpointTemplate: template, - } - return bidder, nil -} - -func (adapter *YeahmobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var adapterRequests []*adapters.RequestData - - adapterRequest, errs := adapter.makeRequest(request) - if errs == nil { - adapterRequests = append(adapterRequests, adapterRequest) - } - - return adapterRequests, errs -} - -func (adapter *YeahmobiAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { - var errs []error - - yeahmobiExt, errs := getYeahmobiExt(request) - - if yeahmobiExt == nil { - return nil, errs - } - endPoint, err := adapter.getEndpoint(yeahmobiExt) - if err != nil { - return nil, append(errs, err) - } - transform(request) - reqBody, err := json.Marshal(request) - - if err != nil { - return nil, append(errs, err) - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - - return &adapters.RequestData{ - Method: "POST", - Uri: endPoint, - Body: reqBody, - Headers: headers, - }, errs -} - -func transform(request *openrtb2.BidRequest) { - for i, imp := range request.Imp { - if imp.Native != nil { - var nativeRequest map[string]interface{} - nativeCopyRequest := make(map[string]interface{}) - err := json.Unmarshal([]byte(request.Imp[i].Native.Request), &nativeRequest) - //just ignore the bad native request - if err == nil { - _, exists := nativeRequest["native"] - if exists { - continue - } - - nativeCopyRequest["native"] = nativeRequest - nativeReqByte, err := json.Marshal(nativeCopyRequest) - //just ignore the bad native request - if err != nil { - continue - } - - nativeCopy := *request.Imp[i].Native - nativeCopy.Request = string(nativeReqByte) - request.Imp[i].Native = &nativeCopy - } - } - } -} - -func getYeahmobiExt(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpYeahmobi, []error) { - var extImpYeahmobi openrtb_ext.ExtImpYeahmobi - var errs []error - - for _, imp := range request.Imp { - var extBidder adapters.ExtImpBidder - err := json.Unmarshal(imp.Ext, &extBidder) - if err != nil { - errs = append(errs, err) - continue - } - err = json.Unmarshal(extBidder.Bidder, &extImpYeahmobi) - if err != nil { - errs = append(errs, err) - continue - } - break - } - - return &extImpYeahmobi, errs - -} - -func (adapter *YeahmobiAdapter) getEndpoint(ext *openrtb_ext.ExtImpYeahmobi) (string, error) { - return macros.ResolveMacros(adapter.EndpointTemplate, macros.EndpointTemplateParams{Host: "gw-" + url.QueryEscape(ext.ZoneId) + "-bid.yeahtargeter.com"}) -} - -// MakeBids make the bids for the bid response. -func (a *YeahmobiAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Unexpected status code: %d.", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unexpected status code: %d.", response.StatusCode), - }} - } - - var bidResp openrtb2.BidResponse - - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{err} - } - - bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - var mediaType = getBidType(sb.Bid[i].ImpID, internalRequest.Imp) - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: mediaType, - }) - } - } - return bidResponse, nil - -} - -func getBidType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { - bidType := openrtb_ext.BidTypeBanner - for _, imp := range imps { - if imp.ID == impId { - if imp.Banner != nil { - break - } - if imp.Video != nil { - bidType = openrtb_ext.BidTypeVideo - break - } - if imp.Native != nil { - bidType = openrtb_ext.BidTypeNative - break - } - - } - } - return bidType -} diff --git a/adapters/yeahmobi/yeahmobi_test.go b/adapters/yeahmobi/yeahmobi_test.go deleted file mode 100644 index 0b1c39ef214..00000000000 --- a/adapters/yeahmobi/yeahmobi_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package yeahmobi - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderYeahmobi, config.Adapter{ - Endpoint: "https://{{.Host}}/prebid/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "yeahmobitest", bidder) -} - -func TestEndpointTemplateMalformed(t *testing.T) { - _, buildErr := Builder(openrtb_ext.BidderYeahmobi, config.Adapter{ - Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - assert.Error(t, buildErr) -} diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/no-bid.json b/adapters/yeahmobi/yeahmobitest/exemplary/no-bid.json deleted file mode 100644 index 723cc40e664..00000000000 --- a/adapters/yeahmobi/yeahmobitest/exemplary/no-bid.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{"w": 300, "h": 50}] - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id":"test-imp-id", - "banner": { - "format": [{"w": 300, "h": 50}] - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - } - }, - "mockResponse": { - "status": 204, - "body": {} - } - } - ], - - "expectedBidResponses": [] - -} diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-banner.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-banner.json deleted file mode 100644 index 7499a7874e7..00000000000 --- a/adapters/yeahmobi/yeahmobitest/exemplary/simple-banner.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{"w": 300, "h": 50}] - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id":"test-imp-id", - "banner": { - "format": [{"w": 300, "h": 50}] - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "ttx", - "bid": [{ - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 1.2, - "adm": "some-ads", - "crid": "crid_testid" - }] - } - ], - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 1.2, - "adm": "some-ads", - "crid": "crid_testid" - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-native-1.1.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-native-1.1.json deleted file mode 100644 index 7e93eb68246..00000000000 --- a/adapters/yeahmobi/yeahmobitest/exemplary/simple-native-1.1.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "native": { - "request": "{\"native\":{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"required\":1,\"img\":{\"type\":3,\"wmin\":128,\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"]}},{\"id\":7,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}}", - "ver": "1.2" - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - }, - "httpcalls": [ - { - "expectedRequest": { - "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "native": { - "request": "{\"native\":{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"required\":1,\"img\":{\"type\":3,\"wmin\":128,\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"]}},{\"id\":7,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}}", - "ver": "1.2" - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "8400d766-58b3-47d4-80d7-6658b337d403", - "impid": "test-imp-id", - "price": 1.2, - "adm": "some ads", - "crid": "crid_testid" - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "8400d766-58b3-47d4-80d7-6658b337d403", - "impid": "test-imp-id", - "price": 1.2, - "adm": "some ads", - "crid": "crid_testid" - - }, - "type": "native" - } - ] - } - ] -} diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-native.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-native.json deleted file mode 100644 index 894e835bc07..00000000000 --- a/adapters/yeahmobi/yeahmobitest/exemplary/simple-native.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "native": { - "request": "{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"required\":1,\"img\":{\"type\":3,\"wmin\":128,\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"]}},{\"id\":7,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}", - "ver": "1.2" - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - }, - "httpcalls": [ - { - "expectedRequest": { - "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "native": { - "request": "{\"native\":{\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"img\":{\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"],\"type\":3,\"wmin\":128},\"required\":1},{\"data\":{\"len\":120,\"type\":2},\"id\":7,\"required\":1}],\"context\":1,\"plcmtcnt\":1,\"plcmttype\":4,\"ver\":\"1.2\"}}", - "ver": "1.2" - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "8400d766-58b3-47d4-80d7-6658b337d403", - "impid": "test-imp-id", - "price": 1.2, - "adm": "some ads", - "crid": "crid_testid" - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "8400d766-58b3-47d4-80d7-6658b337d403", - "impid": "test-imp-id", - "price": 1.2, - "adm": "some ads", - "crid": "crid_testid" - - }, - "type": "native" - } - ] - } - ] -} diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json deleted file mode 100644 index b040d31b5f6..00000000000 --- a/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "w": 300, - "h": 250, - "mimes": [ - "video/mp4" - ] - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id":"test-imp-id", - "video": { - "w": 300, - "h": 250, - "mimes": [ - "video/mp4" - ] - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "ttx", - "bid": [{ - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 1.2, - "adm": "some-ads", - "crid": "crid_testid" - }] - } - ], - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 1.2, - "adm": "some-ads", - "crid": "crid_testid" - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext.json b/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext.json deleted file mode 100644 index 444e1e7a8d8..00000000000 --- a/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{"w": 300, "h": 50}] - }, - "ext": "aaa" - } - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", - "comparison": "literal" - } - ] -} diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext_bidder.json b/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext_bidder.json deleted file mode 100644 index 89697d37141..00000000000 --- a/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext_bidder.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{"w": 300, "h": 50}] - }, - "ext": { - "bidder": "aa" - } - } - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpYeahmobi", - "comparison": "literal" - } - ] -} diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json b/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json deleted file mode 100644 index 0d77e5af93a..00000000000 --- a/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{"w": 300, "h": 50}] - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id":"test-imp-id", - "banner": { - "format": [{"w": 300, "h": 50}] - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": "{\"id\":test-request-id" - } - } - ], - - "expectedMakeBidsErrors": [ - { - "comparison": "literal", - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse" - } - ] -} diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/status_400.json b/adapters/yeahmobi/yeahmobitest/supplemental/status_400.json deleted file mode 100644 index 74bb869218c..00000000000 --- a/adapters/yeahmobi/yeahmobitest/supplemental/status_400.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{"w": 300, "h": 50}] - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id":"test-imp-id", - "banner": { - "format": [{"w": 300, "h": 50}] - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - } - }, - "mockResponse": { - "status": 400, - "body": {} - } - } - ], - - "expectedMakeBidsErrors": [ - { - "comparison": "literal", - "value": "Unexpected status code: 400." - } - ] -} diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/status_500.json b/adapters/yeahmobi/yeahmobitest/supplemental/status_500.json deleted file mode 100644 index 2d3264de897..00000000000 --- a/adapters/yeahmobi/yeahmobitest/supplemental/status_500.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{"w": 300, "h": 50}] - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id":"test-imp-id", - "banner": { - "format": [{"w": 300, "h": 50}] - }, - "ext": { - "bidder": { - "pubId": "fake-pub-id", - "zoneId": "sin" - } - } - } - ] - } - }, - "mockResponse": { - "status": 500, - "body": {} - } - } - ], - - "expectedMakeBidsErrors": [ - { - "comparison": "literal", - "value": "Unexpected status code: 500." - } - ] -} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 527e4c87b4e..d2cb777b18a 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -177,7 +177,6 @@ import ( "github.com/prebid/prebid-server/adapters/vrtcal" "github.com/prebid/prebid-server/adapters/xeworks" "github.com/prebid/prebid-server/adapters/yahooAds" - "github.com/prebid/prebid-server/adapters/yeahmobi" "github.com/prebid/prebid-server/adapters/yieldlab" "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/adapters/yieldone" @@ -388,7 +387,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderYahooAds: yahooAds.Builder, openrtb_ext.BidderYahooAdvertising: yahooAds.Builder, openrtb_ext.BidderYahooSSP: yahooAds.Builder, - openrtb_ext.BidderYeahmobi: yeahmobi.Builder, openrtb_ext.BidderYieldlab: yieldlab.Builder, openrtb_ext.BidderYieldmo: yieldmo.Builder, openrtb_ext.BidderYieldone: yieldone.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 11300d479d2..7b762eb6794 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -118,6 +118,7 @@ func GetDisabledBidderWarningMessages(infos config.BidderInfos) map[string]strin "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, "brightroll": `Bidder "brightroll" is no longer available in Prebid Server. Please update your configuration.`, + "yeahmobi": `Bidder "yeahmobi" is no longer available in Prebid Server. Please update your configuration.`, "ninthdecimal": `Bidder "ninthdecimal" is no longer available in Prebid Server. Please update your configuration.`, "kubient": `Bidder "kubient" is no longer available in Prebid Server. Please update your configuration.`, "definemedia": `Bidder "definemedia" is no longer available in Prebid Server. Please update your configuration.`, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 049ce11d462..0af0badef41 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -215,7 +215,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderYahooAds, BidderYahooAdvertising, BidderYahooSSP, - BidderYeahmobi, BidderYieldlab, BidderYieldmo, BidderYieldone, @@ -506,7 +505,6 @@ const ( BidderYahooAds BidderName = "yahooAds" BidderYahooAdvertising BidderName = "yahooAdvertising" BidderYahooSSP BidderName = "yahoossp" - BidderYeahmobi BidderName = "yeahmobi" BidderYieldlab BidderName = "yieldlab" BidderYieldmo BidderName = "yieldmo" BidderYieldone BidderName = "yieldone" diff --git a/openrtb_ext/imp_yeahmobi.go b/openrtb_ext/imp_yeahmobi.go deleted file mode 100644 index 16948d807c4..00000000000 --- a/openrtb_ext/imp_yeahmobi.go +++ /dev/null @@ -1,7 +0,0 @@ -package openrtb_ext - -// ExtImpYeahmobi defines the contract for bidrequest.imp[i].ext.prebid.bidder.yeahmobi -type ExtImpYeahmobi struct { - PubId string `json:"pubId"` - ZoneId string `json:"zoneId"` -} diff --git a/static/bidder-info/yeahmobi.yaml b/static/bidder-info/yeahmobi.yaml deleted file mode 100644 index ae41464f6fa..00000000000 --- a/static/bidder-info/yeahmobi.yaml +++ /dev/null @@ -1,9 +0,0 @@ -endpoint: "https://{{.Host}}/prebid/bid" -maintainer: - email: "junping.zhao@yeahmobi.com" -capabilities: - app: - mediaTypes: - - banner - - video - - native diff --git a/static/bidder-params/yeahmobi.json b/static/bidder-params/yeahmobi.json deleted file mode 100644 index fe26fa7255a..00000000000 --- a/static/bidder-params/yeahmobi.json +++ /dev/null @@ -1,21 +0,0 @@ - -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Yeahmobi Adapter Params", - "description": "A schema which validates params accepted by the Yeahmobi adapter", - - "type": "object", - "properties": { - "pubId": { - "type": "string", - "description": "Publisher ID", - "minLength": 1 - }, - "zoneId": { - "type": "string", - "description": "Zone Id", - "minLength": 1 - } - }, - "required": ["pubId", "zoneId"] -} \ No newline at end of file From f2230c88b4dc0985c6a8dc7fde08b2189cd64167 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 26 Sep 2023 07:23:39 -0400 Subject: [PATCH 018/138] Remove Adapter: engageBDR (#3131) Co-authored-by: Ashish Garg --- adapters/engagebdr/engagebdr.go | 151 ----------------- adapters/engagebdr/engagebdr_test.go | 20 --- .../engagebdrtest/exemplary/banner.json | 97 ----------- .../engagebdrtest/exemplary/multi-banner.json | 154 ----------------- .../engagebdrtest/exemplary/multi-video.json | 159 ------------------ .../engagebdrtest/exemplary/native.json | 95 ----------- .../engagebdrtest/exemplary/video.json | 100 ----------- .../engagebdrtest/supplemental/audio.json | 25 --- .../supplemental/multi-1-invalid-imp.json | 111 ------------ .../supplemental/no-imp-ext-bidder.json | 24 --- .../supplemental/no-imp-ext.json | 22 --- .../supplemental/no-imp-sspid.json | 26 --- .../engagebdrtest/supplemental/no-imp.json | 15 -- .../supplemental/response-500.json | 52 ------ adapters/engagebdr/params_test.go | 50 ------ exchange/adapter_builders.go | 2 - exchange/adapter_util.go | 1 + openrtb_ext/bidders.go | 2 - openrtb_ext/imp_engagebdr.go | 6 - static/bidder-info/engagebdr.yaml | 14 -- static/bidder-params/engagebdr.json | 14 -- 21 files changed, 1 insertion(+), 1139 deletions(-) delete mode 100644 adapters/engagebdr/engagebdr.go delete mode 100644 adapters/engagebdr/engagebdr_test.go delete mode 100644 adapters/engagebdr/engagebdrtest/exemplary/banner.json delete mode 100644 adapters/engagebdr/engagebdrtest/exemplary/multi-banner.json delete mode 100644 adapters/engagebdr/engagebdrtest/exemplary/multi-video.json delete mode 100644 adapters/engagebdr/engagebdrtest/exemplary/native.json delete mode 100644 adapters/engagebdr/engagebdrtest/exemplary/video.json delete mode 100644 adapters/engagebdr/engagebdrtest/supplemental/audio.json delete mode 100644 adapters/engagebdr/engagebdrtest/supplemental/multi-1-invalid-imp.json delete mode 100644 adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext-bidder.json delete mode 100644 adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext.json delete mode 100644 adapters/engagebdr/engagebdrtest/supplemental/no-imp-sspid.json delete mode 100644 adapters/engagebdr/engagebdrtest/supplemental/no-imp.json delete mode 100644 adapters/engagebdr/engagebdrtest/supplemental/response-500.json delete mode 100644 adapters/engagebdr/params_test.go delete mode 100644 openrtb_ext/imp_engagebdr.go delete mode 100644 static/bidder-info/engagebdr.yaml delete mode 100644 static/bidder-params/engagebdr.json diff --git a/adapters/engagebdr/engagebdr.go b/adapters/engagebdr/engagebdr.go deleted file mode 100644 index eb0160172a0..00000000000 --- a/adapters/engagebdr/engagebdr.go +++ /dev/null @@ -1,151 +0,0 @@ -package engagebdr - -import ( - "encoding/json" - "net/http" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - - "fmt" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" -) - -type EngageBDRAdapter struct { - URI string -} - -func (adapter *EngageBDRAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - - errors := make([]error, 0, len(request.Imp)) - - if request.Imp == nil || len(request.Imp) == 0 { - errors = append(errors, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid BidRequest. No valid imp."), - }) - return nil, errors - } - - // EngageBDR uses different sspid parameters for banner and video. - sspidImps := make(map[string][]openrtb2.Imp) - for _, imp := range request.Imp { - - if imp.Audio != nil { - errors = append(errors, &errortypes.BadInput{ - Message: fmt.Sprintf("Ignoring imp id=%s, invalid MediaType. EngageBDR only supports Banner, Video and Native.", imp.ID), - }) - continue - } - - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - errors = append(errors, &errortypes.BadInput{ - Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding extImpBidder, err: %s.", imp.ID, err), - }) - continue - } - impExt := openrtb_ext.ExtImpEngageBDR{} - err := json.Unmarshal(bidderExt.Bidder, &impExt) - if err != nil { - errors = append(errors, &errortypes.BadInput{ - Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding impExt, err: %s.", imp.ID, err), - }) - continue - } - if impExt.Sspid == "" { - errors = append(errors, &errortypes.BadInput{ - Message: fmt.Sprintf("Ignoring imp id=%s, no sspid present.", imp.ID), - }) - continue - } - sspidImps[impExt.Sspid] = append(sspidImps[impExt.Sspid], imp) - } - - var adapterRequests []*adapters.RequestData - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - - for sspid, imps := range sspidImps { - if len(imps) > 0 { - // Make a copy as we don't want to change the original request - reqCopy := *request - reqCopy.Imp = imps - reqJSON, err := json.Marshal(reqCopy) - if err != nil { - errors = append(errors, err) - return nil, errors - } - adapterReq := adapters.RequestData{ - Method: "POST", - Uri: adapter.URI + "?zoneid=" + sspid, - Body: reqJSON, - Headers: headers, - } - adapterRequests = append(adapterRequests, &adapterReq) - } - } - - return adapterRequests, errors -} - -func (adapter *EngageBDRAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{err} - } - - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp), - }) - } - } - return bidResponse, nil -} - -func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { - mediaType := openrtb_ext.BidTypeBanner - for _, imp := range imps { - if imp.ID == impId { - if imp.Video != nil { - mediaType = openrtb_ext.BidTypeVideo - } else if imp.Native != nil { - mediaType = openrtb_ext.BidTypeNative - } - return mediaType - } - } - return mediaType -} - -// Builder builds a new instance of the EngageBDR adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &EngageBDRAdapter{ - URI: config.Endpoint, - } - return bidder, nil -} diff --git a/adapters/engagebdr/engagebdr_test.go b/adapters/engagebdr/engagebdr_test.go deleted file mode 100644 index 0877750cb19..00000000000 --- a/adapters/engagebdr/engagebdr_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package engagebdr - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderEngageBDR, config.Adapter{ - Endpoint: "http://dsp.bnmla.com/hb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "engagebdrtest", bidder) -} diff --git a/adapters/engagebdr/engagebdrtest/exemplary/banner.json b/adapters/engagebdr/engagebdrtest/exemplary/banner.json deleted file mode 100644 index 92b79e8f349..00000000000 --- a/adapters/engagebdr/engagebdrtest/exemplary/banner.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid": "99999" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99999", - "body":{ - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid":"99999" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id" : "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "
", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - } - ], - "seat": "test-request-id" - } - ], - "bidid": "test-request-id", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "
", - "adomain": ["advertiserdomain.com"], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/exemplary/multi-banner.json b/adapters/engagebdr/engagebdrtest/exemplary/multi-banner.json deleted file mode 100644 index d11e38c46fc..00000000000 --- a/adapters/engagebdr/engagebdrtest/exemplary/multi-banner.json +++ /dev/null @@ -1,154 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid": "99999" - } - } - }, - { - "id": "test-imp-id-2", - "banner": { - "w": 320, - "h": 50 - }, - "ext": { - "bidder": { - "sspid": "99999" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99999", - "body":{ - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid":"99999" - } - } - }, - { - "id": "test-imp-id-2", - "banner": { - "w": 320, - "h": 50 - }, - "ext": { - "bidder": { - "sspid":"99999" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id" : "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "
", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - { - "id" : "test-imp-id-2", - "impid": "test-imp-id-2", - "price": 7.50, - "adid": "abcde-12345-2", - "adm": "
", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345-2", - "w": 320, - "h": 50 - } - ], - "seat": "test-request-id" - } - ], - "bidid": "test-request-id", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "
", - "adomain": ["advertiserdomain.com"], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - "type": "banner" - }, - { - "bid": { - "id": "test-imp-id-2", - "impid": "test-imp-id-2", - "price": 7.50, - "adid": "abcde-12345-2", - "adm": "
", - "adomain": ["advertiserdomain.com"], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345-2", - "w": 320, - "h": 50 - }, - "type": "banner" - } - ] - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/exemplary/multi-video.json b/adapters/engagebdr/engagebdrtest/exemplary/multi-video.json deleted file mode 100644 index 9506c963578..00000000000 --- a/adapters/engagebdr/engagebdrtest/exemplary/multi-video.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "w": 300, - "mimes": null, - "h": 250 - }, - "ext": { - "bidder": { - "sspid": "99998" - } - } - }, - { - "id": "test-imp-id-2", - "video": { - "w": 320, - "mimes": null, - "h": 50 - }, - "ext": { - "bidder": { - "sspid": "99998" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99998", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "w": 300, - "mimes": null, - "h": 250 - }, - "ext": { - "bidder": { - "sspid": "99998" - } - } - }, - { - "id": "test-imp-id-2", - "video": { - "w": 320, - "mimes": null, - "h": 50 - }, - "ext": { - "bidder": { - "sspid": "99998" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "\nStatic VASTStatic VAST Tag", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "https://cdn0.bnmla.com/vtest.xml", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - { - "id": "test-imp-id-2", - "impid": "test-imp-id-2", - "price": 7.81, - "adid": "abcde-12345-2", - "adm": "\nStatic VASTStatic VAST Tag", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "https://cdn0.bnmla.com/vtest.xml", - "cid": "campaign1", - "crid": "abcde-12345-2", - "w": 320, - "h": 50 - } - ], - "seat": "test-request-id" - } - ], - "bidid": "test-request-id", - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "\nStatic VASTStatic VAST Tag", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "https://cdn0.bnmla.com/vtest.xml", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - "type": "video" - }, - { - "bid": { - "id": "test-imp-id-2", - "impid": "test-imp-id-2", - "price": 7.81, - "adid": "abcde-12345-2", - "adm": "\nStatic VASTStatic VAST Tag", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "https://cdn0.bnmla.com/vtest.xml", - "cid": "campaign1", - "crid": "abcde-12345-2", - "w": 320, - "h": 50 - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/engagebdr/engagebdrtest/exemplary/native.json b/adapters/engagebdr/engagebdrtest/exemplary/native.json deleted file mode 100644 index 963194fb8bd..00000000000 --- a/adapters/engagebdr/engagebdrtest/exemplary/native.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "native": { - "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", - "ver":"1.1" - }, - "ext": { - "bidder": { - "sspid": "99997" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99997", - "body":{ - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "native": { - "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", - "ver":"1.1" - }, - "ext": { - "bidder": { - "sspid":"99997" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id" : "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "{\"native\":{\"link\":{\"url\":\"https://rtb-use.mfadsrvr.com/click/ESoNneAwqCPnn97YSh0EoJzPUnSmqwdERCYPCdrHr1_TJz_V-x2xjMgxcROeooIH5fe1exAsWt2aqg1ESQEVQM8i0TpI1QBcV4V87Uzmf_XfAR6-6xqvqfGuDs-pJDWqAYz0P0OtHlrvVztlMdWu6JT9_GAtVAnB9gp0JchRJLSqr1h_GRZwuNUri7NvveTD7m8ZUHKNFldKPwHCbom120NFFn2Z3a6v0owsZfIgOff-1YyvZ9WkzVr3755kGRT_D1FUy3r2kurY8HdfeTiRuZAajluniEkJql7yGlS6hVfQ3vT3X93BKIo1F_A3o4bfywT49tM-3l2X8vwlc-w9X-B5VudQPJ8kboJZ2OuaD5AN///\"},\"assets\":[{\"id\":0,\"title\":{\"text\":\"4 Signs Your Heart Is Quietly Failing You\"},\"required\":1},{\"id\":3,\"img\":{\"w\":1200,\"h\":627,\"url\":\"https://de9a11s35xj3d.cloudfront.net/5922785fd53de8084607850abdaace4f.jpg\"}},{\"id\":4,\"data\":{\"value\":\"PhysioTru\"}},{\"id\":6,\"data\":{\"value\":\"\\n How To Avoid A Heart Attack (Do This For 7 Seconds Twice A Day)\\n \"}}],\"imptrackers\":[\"https://rtb-use.mfadsrvr.com/imp_c2s/v1/ESoNneAwqCPnn97YSh0EoJzPUnSmqwdERCYPCdrHr1_TJz_V-x2xjMgxcROeooIH5fe1exAsWt2aqg1ESQEVQM8i0TpI1QBcV4V87Uzmf_XfAR6-6xqvqfGuDs-pJDWqAYz0P0OtHlrvVztlMdWu6JT9_GAtVAnB9gp0JchRJLSqr1h_GRZwuNUri7NvveTD7m8ZUHKNFldKPwHCbom120NFFn2Z3a6v0owsZfIgOff-1YyvZ9WkzVr3755kGRT_D1FUy3r2kurY8HdfeTiRuZAajluniEkJql7yGlS6hVfQ3vT3X93BKIo1F_A3o4bfywT49tM-3l2X8vwlc-w9X-B5VudQPJ8kboJZ2OuaD5AN/${AUCTION_PRICE}\"],\"ver\":1}}", - "adomain": [ - "advertiserdomain.com" - ], - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - } - ], - "seat": "test-request-id" - } - ], - "bidid": "test-request-id", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "{\"native\":{\"link\":{\"url\":\"https://rtb-use.mfadsrvr.com/click/ESoNneAwqCPnn97YSh0EoJzPUnSmqwdERCYPCdrHr1_TJz_V-x2xjMgxcROeooIH5fe1exAsWt2aqg1ESQEVQM8i0TpI1QBcV4V87Uzmf_XfAR6-6xqvqfGuDs-pJDWqAYz0P0OtHlrvVztlMdWu6JT9_GAtVAnB9gp0JchRJLSqr1h_GRZwuNUri7NvveTD7m8ZUHKNFldKPwHCbom120NFFn2Z3a6v0owsZfIgOff-1YyvZ9WkzVr3755kGRT_D1FUy3r2kurY8HdfeTiRuZAajluniEkJql7yGlS6hVfQ3vT3X93BKIo1F_A3o4bfywT49tM-3l2X8vwlc-w9X-B5VudQPJ8kboJZ2OuaD5AN///\"},\"assets\":[{\"id\":0,\"title\":{\"text\":\"4 Signs Your Heart Is Quietly Failing You\"},\"required\":1},{\"id\":3,\"img\":{\"w\":1200,\"h\":627,\"url\":\"https://de9a11s35xj3d.cloudfront.net/5922785fd53de8084607850abdaace4f.jpg\"}},{\"id\":4,\"data\":{\"value\":\"PhysioTru\"}},{\"id\":6,\"data\":{\"value\":\"\\n How To Avoid A Heart Attack (Do This For 7 Seconds Twice A Day)\\n \"}}],\"imptrackers\":[\"https://rtb-use.mfadsrvr.com/imp_c2s/v1/ESoNneAwqCPnn97YSh0EoJzPUnSmqwdERCYPCdrHr1_TJz_V-x2xjMgxcROeooIH5fe1exAsWt2aqg1ESQEVQM8i0TpI1QBcV4V87Uzmf_XfAR6-6xqvqfGuDs-pJDWqAYz0P0OtHlrvVztlMdWu6JT9_GAtVAnB9gp0JchRJLSqr1h_GRZwuNUri7NvveTD7m8ZUHKNFldKPwHCbom120NFFn2Z3a6v0owsZfIgOff-1YyvZ9WkzVr3755kGRT_D1FUy3r2kurY8HdfeTiRuZAajluniEkJql7yGlS6hVfQ3vT3X93BKIo1F_A3o4bfywT49tM-3l2X8vwlc-w9X-B5VudQPJ8kboJZ2OuaD5AN/${AUCTION_PRICE}\"],\"ver\":1}}", - "adomain": ["advertiserdomain.com"], - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - "type": "native" - } - ] - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/exemplary/video.json b/adapters/engagebdr/engagebdrtest/exemplary/video.json deleted file mode 100644 index 53c00dc4523..00000000000 --- a/adapters/engagebdr/engagebdrtest/exemplary/video.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "w": 300, - "mimes": null, - "h": 250 - }, - "ext": { - "bidder": { - "sspid":"99998" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99998", - "body":{ - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "video": { - "w": 300, - "mimes": null, - "h": 250 - }, - "ext": { - "bidder": { - "sspid":"99998" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "\nStatic VASTStatic VAST Tag", - "adomain":[ - "advertiserdomain.com" - ], - "iurl": "https://cdn0.bnmla.com/vtest.xml", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - } - ], - "seat": "test-request-id" - } - ], - "bidid": "test-request-id", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "\nStatic VASTStatic VAST Tag", - "adomain": ["advertiserdomain.com"], - "iurl": "https://cdn0.bnmla.com/vtest.xml", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - "type": "video" - } - ] - - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/audio.json b/adapters/engagebdr/engagebdrtest/supplemental/audio.json deleted file mode 100644 index e03fdb50aa4..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/audio.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "audio": { - }, - "ext": { - "bidder": { - "sspid": "99996" - } - } - } - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "Ignoring imp id=test-imp-id, invalid MediaType. EngageBDR only supports Banner, Video and Native.", - "comparison": "literal" - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/multi-1-invalid-imp.json b/adapters/engagebdr/engagebdrtest/supplemental/multi-1-invalid-imp.json deleted file mode 100644 index a2cd79c9deb..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/multi-1-invalid-imp.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid": "99999" - } - } - }, - { - "id": "test-imp-id-2", - "banner": { - "w": 320, - "h": 50 - }, - "ext": { - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "Ignoring imp id=test-imp-id-2, error while decoding impExt, err: unexpected end of JSON input.", - "comparison": "literal" - } - ], - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99999", - "body":{ - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid":"99999" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id" : "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "
", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - } - ], - "seat": "test-request-id" - } - ], - "bidid": "test-request-id", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "
", - "adomain": ["advertiserdomain.com"], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext-bidder.json b/adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext-bidder.json deleted file mode 100644 index 9aa8177fc8b..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext-bidder.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - } - } - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "Ignoring imp id=test-imp-id, error while decoding impExt, err: unexpected end of JSON input.", - "comparison": "literal" - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext.json b/adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext.json deleted file mode 100644 index 04e167fd671..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - } - } - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "Ignoring imp id=test-imp-id, error while decoding extImpBidder, err: unexpected end of JSON input.", - "comparison": "literal" - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/no-imp-sspid.json b/adapters/engagebdr/engagebdrtest/supplemental/no-imp-sspid.json deleted file mode 100644 index d193bf779fc..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/no-imp-sspid.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - } - } - } - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "Ignoring imp id=test-imp-id, no sspid present.", - "comparison": "literal" - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/no-imp.json b/adapters/engagebdr/engagebdrtest/supplemental/no-imp.json deleted file mode 100644 index c5b0fa96042..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/no-imp.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "Invalid BidRequest. No valid imp.", - "comparison": "literal" - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/response-500.json b/adapters/engagebdr/engagebdrtest/supplemental/response-500.json deleted file mode 100644 index ce750770a63..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/response-500.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid": "99999" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99999", - "body":{ - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid":"99999" - } - } - }] - } - }, - "mockResponse": { - "status": 500 - } - } - ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] -} - diff --git a/adapters/engagebdr/params_test.go b/adapters/engagebdr/params_test.go deleted file mode 100644 index c797d04ecc8..00000000000 --- a/adapters/engagebdr/params_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package engagebdr - -import ( - "encoding/json" - "testing" - - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestValidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderEngageBDR, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected beachfront params: %s", validParam) - } - } -} - -func TestInvalidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderEngageBDR, json.RawMessage(invalidParam)); err == nil { - t.Errorf("Schema allowed unexpected params: %s", invalidParam) - } - } -} - -var validParams = []string{ - `{"sspid":"12345"}`, -} - -var invalidParams = []string{ - ``, - `null`, - `true`, - `5`, - `4.2`, - `[]`, - `{}`, - `{"sspid":null}`, - `{"appId":"11bc5dd5-7421-4dd8-c926-40fa653bec76"}`, -} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index d2cb777b18a..3a2f871e199 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -75,7 +75,6 @@ import ( "github.com/prebid/prebid-server/adapters/dmx" evolution "github.com/prebid/prebid-server/adapters/e_volution" "github.com/prebid/prebid-server/adapters/emtv" - "github.com/prebid/prebid-server/adapters/engagebdr" "github.com/prebid/prebid-server/adapters/eplanning" "github.com/prebid/prebid-server/adapters/epom" "github.com/prebid/prebid-server/adapters/flipp" @@ -267,7 +266,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderDmx: dmx.Builder, openrtb_ext.BidderEmtv: emtv.Builder, openrtb_ext.BidderEmxDigital: cadentaperturemx.Builder, - openrtb_ext.BidderEngageBDR: engagebdr.Builder, openrtb_ext.BidderEPlanning: eplanning.Builder, openrtb_ext.BidderEpom: epom.Builder, openrtb_ext.BidderEpsilon: conversant.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 7b762eb6794..8ce6f0df67c 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -118,6 +118,7 @@ func GetDisabledBidderWarningMessages(infos config.BidderInfos) map[string]strin "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, "brightroll": `Bidder "brightroll" is no longer available in Prebid Server. Please update your configuration.`, + "engagebdr": `Bidder "engagebdr" is no longer available in Prebid Server. Please update your configuration.`, "yeahmobi": `Bidder "yeahmobi" is no longer available in Prebid Server. Please update your configuration.`, "ninthdecimal": `Bidder "ninthdecimal" is no longer available in Prebid Server. Please update your configuration.`, "kubient": `Bidder "kubient" is no longer available in Prebid Server. Please update your configuration.`, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 0af0badef41..34c01f1ce32 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -95,7 +95,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderDmx, BidderEmtv, BidderEmxDigital, - BidderEngageBDR, BidderEPlanning, BidderEpom, BidderEpsilon, @@ -385,7 +384,6 @@ const ( BidderDmx BidderName = "dmx" BidderEmtv BidderName = "emtv" BidderEmxDigital BidderName = "emx_digital" - BidderEngageBDR BidderName = "engagebdr" BidderEPlanning BidderName = "eplanning" BidderEpsilon BidderName = "epsilon" BidderEpom BidderName = "epom" diff --git a/openrtb_ext/imp_engagebdr.go b/openrtb_ext/imp_engagebdr.go deleted file mode 100644 index db500111a78..00000000000 --- a/openrtb_ext/imp_engagebdr.go +++ /dev/null @@ -1,6 +0,0 @@ -package openrtb_ext - -// ExtImpEngageBDR defines the contract for bidrequest.imp[i].ext.prebid.bidder.engagebdr -type ExtImpEngageBDR struct { - Sspid string `json:"sspid"` -} diff --git a/static/bidder-info/engagebdr.yaml b/static/bidder-info/engagebdr.yaml deleted file mode 100644 index 8218040c605..00000000000 --- a/static/bidder-info/engagebdr.yaml +++ /dev/null @@ -1,14 +0,0 @@ -endpoint: "http://dsp.bnmla.com/hb" -maintainer: - email: "tech@engagebdr.com" -capabilities: - app: - mediaTypes: - - banner - - video - - native -userSync: - iframe: - url: "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" - userMacro: "${UUID}" - diff --git a/static/bidder-params/engagebdr.json b/static/bidder-params/engagebdr.json deleted file mode 100644 index 4f987004045..00000000000 --- a/static/bidder-params/engagebdr.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "EngageBDR Adapter Params", - "description": "A schema which validates params accepted by the EngageBDR adapter", - "type": "object", - "properties": { - "sspid": { - "type": "string", - "description": "SSPID parameter", - "pattern": "^[0-9]+$" - } - }, - "required": ["sspid"] -} From 0d96c9d1799b3634cd587fdca8ed0d71f6541f1b Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 26 Sep 2023 07:57:27 -0400 Subject: [PATCH 019/138] Remove Adapter: Nano Interactive (#3134) Co-authored-by: Ashish Garg --- adapters/nanointeractive/nanointeractive.go | 164 ------------------ .../nanointeractive/nanointeractive_test.go | 20 --- .../exemplary/simple-banner.json | 88 ---------- .../supplemental/bad_response.json | 63 ------- .../supplemental/invalid-params.json | 81 --------- .../supplemental/multi-param.json | 149 ---------------- .../supplemental/status_204.json | 58 ------- .../supplemental/status_400.json | 63 ------- .../supplemental/status_418.json | 63 ------- adapters/nanointeractive/params_test.go | 63 ------- exchange/adapter_builders.go | 2 - exchange/adapter_util.go | 33 ++-- openrtb_ext/bidders.go | 2 - openrtb_ext/imp_nanointeractive.go | 10 -- static/bidder-info/nanointeractive.yaml | 15 -- static/bidder-params/nanointeractive.json | 32 ---- 16 files changed, 17 insertions(+), 889 deletions(-) delete mode 100644 adapters/nanointeractive/nanointeractive.go delete mode 100644 adapters/nanointeractive/nanointeractive_test.go delete mode 100644 adapters/nanointeractive/nanointeractivetest/exemplary/simple-banner.json delete mode 100644 adapters/nanointeractive/nanointeractivetest/supplemental/bad_response.json delete mode 100644 adapters/nanointeractive/nanointeractivetest/supplemental/invalid-params.json delete mode 100644 adapters/nanointeractive/nanointeractivetest/supplemental/multi-param.json delete mode 100644 adapters/nanointeractive/nanointeractivetest/supplemental/status_204.json delete mode 100644 adapters/nanointeractive/nanointeractivetest/supplemental/status_400.json delete mode 100644 adapters/nanointeractive/nanointeractivetest/supplemental/status_418.json delete mode 100644 adapters/nanointeractive/params_test.go delete mode 100644 openrtb_ext/imp_nanointeractive.go delete mode 100644 static/bidder-info/nanointeractive.yaml delete mode 100644 static/bidder-params/nanointeractive.json diff --git a/adapters/nanointeractive/nanointeractive.go b/adapters/nanointeractive/nanointeractive.go deleted file mode 100644 index d8e3c709525..00000000000 --- a/adapters/nanointeractive/nanointeractive.go +++ /dev/null @@ -1,164 +0,0 @@ -package nanointeractive - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" -) - -type NanoInteractiveAdapter struct { - endpoint string -} - -func (a *NanoInteractiveAdapter) MakeRequests(bidRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - - var errs []error - var validImps []openrtb2.Imp - - var adapterRequests []*adapters.RequestData - var referer string = "" - - for i := 0; i < len(bidRequest.Imp); i++ { - - ref, err := checkImp(&bidRequest.Imp[i]) - - // If the parsing is failed, remove imp and add the error. - if err != nil { - errs = append(errs, err) - continue - } - if referer == "" && ref != "" { - referer = ref - } - validImps = append(validImps, bidRequest.Imp[i]) - } - - if len(validImps) == 0 { - errs = append(errs, fmt.Errorf("no impressions in the bid request")) - return nil, errs - } - - // set referer origin - if referer != "" { - if bidRequest.Site == nil { - bidRequest.Site = &openrtb2.Site{} - } - bidRequest.Site.Ref = referer - } - - bidRequest.Imp = validImps - - reqJSON, err := json.Marshal(bidRequest) - if err != nil { - errs = append(errs, err) - return nil, errs - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - headers.Add("x-openrtb-version", "2.5") - if bidRequest.Device != nil { - headers.Add("User-Agent", bidRequest.Device.UA) - headers.Add("X-Forwarded-For", bidRequest.Device.IP) - } - if bidRequest.Site != nil { - headers.Add("Referer", bidRequest.Site.Page) - } - - // set user's cookie - if bidRequest.User != nil && bidRequest.User.BuyerUID != "" { - headers.Add("Cookie", "Nano="+bidRequest.User.BuyerUID) - } - - adapterRequests = append(adapterRequests, &adapters.RequestData{ - Method: "POST", - Uri: a.endpoint, - Body: reqJSON, - Headers: headers, - }) - - return adapterRequests, errs -} - -func (a *NanoInteractiveAdapter) MakeBids( - internalRequest *openrtb2.BidRequest, - externalRequest *adapters.RequestData, - response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - - if response.StatusCode == http.StatusNoContent { - return nil, nil - } else if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: "Invalid request.", - }} - } else if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("unexpected HTTP status %d.", response.StatusCode), - }} - } - - var openRtbBidResponse openrtb2.BidResponse - - if err := json.Unmarshal(response.Body, &openRtbBidResponse); err != nil { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("bad server body response"), - }} - } - - bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(openRtbBidResponse.SeatBid[0].Bid)) - bidResponse.Currency = openRtbBidResponse.Cur - - sb := openRtbBidResponse.SeatBid[0] - for i := 0; i < len(sb.Bid); i++ { - if !(sb.Bid[i].Price > 0) { - continue - } - bid := sb.Bid[i] - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: openrtb_ext.BidTypeBanner, - }) - } - return bidResponse, nil -} - -func checkImp(imp *openrtb2.Imp) (string, error) { - // We support only banner impression - if imp.Banner == nil { - return "", fmt.Errorf("invalid MediaType. NanoInteractive only supports Banner type. ImpID=%s", imp.ID) - } - - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return "", fmt.Errorf("ext not provided; ImpID=%s", imp.ID) - } - - var nanoExt openrtb_ext.ExtImpNanoInteractive - if err := json.Unmarshal(bidderExt.Bidder, &nanoExt); err != nil { - return "", fmt.Errorf("ext.bidder not provided; ImpID=%s", imp.ID) - } - if nanoExt.Pid == "" { - return "", fmt.Errorf("pid is empty; ImpID=%s", imp.ID) - } - - if nanoExt.Ref != "" { - return string(nanoExt.Ref), nil - } - - return "", nil -} - -// Builder builds a new instance of the NanoInteractive adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &NanoInteractiveAdapter{ - endpoint: config.Endpoint, - } - return bidder, nil -} diff --git a/adapters/nanointeractive/nanointeractive_test.go b/adapters/nanointeractive/nanointeractive_test.go deleted file mode 100644 index fb108b8dd58..00000000000 --- a/adapters/nanointeractive/nanointeractive_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package nanointeractive - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderNanoInteractive, config.Adapter{ - Endpoint: "https://ad.audiencemanager.de/hbs"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "nanointeractivetest", bidder) -} diff --git a/adapters/nanointeractive/nanointeractivetest/exemplary/simple-banner.json b/adapters/nanointeractive/nanointeractivetest/exemplary/simple-banner.json deleted file mode 100644 index 727010b93fe..00000000000 --- a/adapters/nanointeractive/nanointeractivetest/exemplary/simple-banner.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{"w": 300, "h": 250}] - }, - "ext": { - "bidder": { - "pid": "58bfec94eb0a1916fa380163" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://ad.audiencemanager.de/hbs", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{ "w": 300,"h": 250} - ] - }, - "ext": { - "bidder": { - "pid": "58bfec94eb0a1916fa380163" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "nanointeractive", - "bid": [{ - "id": "1", - "impid": "test-imp-id", - "price": 0.4580126, - "adm": "", - "adid": "test_ad_id", - "adomain": ["audiencemanager.de"], - "cid": "test_cid", - "crid": "test_banner_crid", - "h": 250, - "w": 300 - }] - } - ], - "bidid": "5a7789eg2662b524d8d7264a96", - "cur": "EUR" - } - } - } - ], - - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "1", - "impid": "test-imp-id", - "price": 0.4580126, - "adm": "", - "adid": "test_ad_id", - "adomain": ["audiencemanager.de"], - "cid": "test_cid", - "crid": "test_banner_crid", - "h": 250, - "w": 300 - }, - "type": "banner" - }] - } - ] -} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/bad_response.json b/adapters/nanointeractive/nanointeractivetest/supplemental/bad_response.json deleted file mode 100644 index 587c952a042..00000000000 --- a/adapters/nanointeractive/nanointeractivetest/supplemental/bad_response.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "pid": "213" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://ad.audiencemanager.de/hbs", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "pid": "213" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": "{\"id\"data.lost" - } - } - ], - - "expectedMakeBidsErrors": [ - { - "value": "bad server body response", - "comparison": "literal" - } - ] -} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/invalid-params.json b/adapters/nanointeractive/nanointeractivetest/supplemental/invalid-params.json deleted file mode 100644 index 631dc99e5a8..00000000000 --- a/adapters/nanointeractive/nanointeractivetest/supplemental/invalid-params.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id-1", - "banner": {}, - "ext": { - "bidder": {} - } - }, - { - "id": "test-imp-id-2", - "banner": { - "format": [{"w": 300, "h": 250}] - }, - "ext": { - - } - }, - { - "id": "test-imp-id-3", - "banner": { - "format": [{"w": 300, "h": 250}] - } - }, - { - "id": "test-imp-id-4", - "video": {}, - "ext": { - "bidder": {} - } - }, - { - "id": "test-imp-id-5", - "audio": { - "startdelay": 0, - "api": [] - }, - "ext": { - "bidder": {} - } - } - ], - "site": { - "id": "siteID", - "publisher": { - "id": "1234" - } - }, - "device": { - "os": "android" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "pid is empty; ImpID=test-imp-id-1", - "comparison": "literal" - }, - { - "value": "ext.bidder not provided; ImpID=test-imp-id-2", - "comparison": "literal" - }, - { - "value": "ext not provided; ImpID=test-imp-id-3", - "comparison": "literal" - }, - { - "value": "invalid MediaType. NanoInteractive only supports Banner type. ImpID=test-imp-id-4", - "comparison": "literal" - }, - { - "value": "invalid MediaType. NanoInteractive only supports Banner type. ImpID=test-imp-id-5", - "comparison": "literal" - }, - { - "value": "no impressions in the bid request", - "comparison": "literal" - } - ] -} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/multi-param.json b/adapters/nanointeractive/nanointeractivetest/supplemental/multi-param.json deleted file mode 100644 index 4f501901e2a..00000000000 --- a/adapters/nanointeractive/nanointeractivetest/supplemental/multi-param.json +++ /dev/null @@ -1,149 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{"w": 300, "h": 250}] - }, - "ext": { - "bidder": { - "pid": "58bfec94eb0a1916fa380163", - "ref": "https://nanointeractive.com" - } - } - }, - { - "id": "test-imp-id2", - "banner": { - "format": [{"w": 300, "h": 250}] - }, - "ext": { - "bidder": { - "pid": "58bfec94eb0a1916fa380163", - "nq": ["search query"], - "category": "Automotive", - "subId": "a23", - "ref": "https://nanointeractive.com" - } - } - } - ], - "device": { - "ip": "127.0.0.1", - "ua": "user_agent" - }, - "user": { - "buyeruid": "userId" - } - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://ad.audiencemanager.de/hbs", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{ "w": 300,"h": 250} - ] - }, - "ext": { - "bidder": { - "pid": "58bfec94eb0a1916fa380163", - "ref": "https://nanointeractive.com" - } - } - }, - { - "id": "test-imp-id2", - "banner": { - "format": [{ "w": 300,"h": 250} - ] - }, - "ext": { - "bidder": { - "pid": "58bfec94eb0a1916fa380163", - "nq": ["search query"], - "category": "Automotive", - "subId": "a23", - "ref": "https://nanointeractive.com" - } - } - } - ], - "site": { - "ref": "https://nanointeractive.com" - }, - "device": { - "ip": "127.0.0.1", - "ua": "user_agent" - }, - "user": { - "buyeruid": "userId" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "nanointeractive", - "bid": [{ - "id": "1", - "impid": "test-imp-id", - "price": 0.4580126, - "adm": "", - "adid": "test_ad_id", - "adomain": ["audiencemanager.de"], - "cid": "test_cid", - "crid": "test_banner_crid", - "h": 250, - "w": 300 - },{ - "id": "2", - "impid": "test-imp-id2", - "price": 0, - "adm": "", - "adid": "test_ad_id", - "adomain": ["audiencemanager.de"], - "cid": "test_cid", - "crid": "test_banner_crid", - "h": 250, - "w": 300 - }] - } - ], - "bidid": "5a7789eg2662b524d8d7264a96", - "cur": "EUR" - } - } - } - ], - - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "1", - "impid": "test-imp-id", - "price": 0.4580126, - "adm": "", - "adid": "test_ad_id", - "adomain": ["audiencemanager.de"], - "cid": "test_cid", - "crid": "test_banner_crid", - "h": 250, - "w": 300 - }, - "type": "banner" - }] - } - ] -} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/status_204.json b/adapters/nanointeractive/nanointeractivetest/supplemental/status_204.json deleted file mode 100644 index ed4d8ff38b8..00000000000 --- a/adapters/nanointeractive/nanointeractivetest/supplemental/status_204.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "pid": "123" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://ad.audiencemanager.de/hbs", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "pid": "123" - } - } - } - ] - } - }, - "mockResponse": { - "status": 204, - "body": {} - } - } - ], - - "expectedBidResponses": [] -} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/status_400.json b/adapters/nanointeractive/nanointeractivetest/supplemental/status_400.json deleted file mode 100644 index f02bd478656..00000000000 --- a/adapters/nanointeractive/nanointeractivetest/supplemental/status_400.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "pid": "123" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://ad.audiencemanager.de/hbs", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "pid": "123" - } - } - } - ] - } - }, - "mockResponse": { - "status": 400, - "body": {} - } - } - ], - - "expectedMakeBidsErrors": [ - { - "value": "Invalid request.", - "comparison": "literal" - } - ] -} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/status_418.json b/adapters/nanointeractive/nanointeractivetest/supplemental/status_418.json deleted file mode 100644 index b7ed65da2af..00000000000 --- a/adapters/nanointeractive/nanointeractivetest/supplemental/status_418.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "pid": "123" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://ad.audiencemanager.de/hbs", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "pid": "123" - } - } - } - ] - } - }, - "mockResponse": { - "status": 418, - "body": {} - } - } - ], - - "expectedMakeBidsErrors": [ - { - "value": "unexpected HTTP status 418.", - "comparison": "literal" - } - ] -} diff --git a/adapters/nanointeractive/params_test.go b/adapters/nanointeractive/params_test.go deleted file mode 100644 index a50425c770b..00000000000 --- a/adapters/nanointeractive/params_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package nanointeractive - -import ( - "encoding/json" - "testing" - - "github.com/prebid/prebid-server/openrtb_ext" -) - -// This file actually intends to test static/bidder-params/nanointeractive.json -// -// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.nanointeracive - -// TestValidParams makes sure that the NanoInteractive schema accepts all imp.ext fields which we intend to support. -func TestValidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderNanoInteractive, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected NanoInteractive params: %s", validParam) - } - } -} - -// TestInvalidParams makes sure that the Marsmedia schema rejects all the imp.ext fields we don't support. -func TestInvalidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderNanoInteractive, json.RawMessage(invalidParam)); err == nil { - t.Errorf("Schema allowed unexpected params: %s", invalidParam) - } - } -} - -var validParams = []string{ - `{"pid": "dafad098"}`, - `{"pid":"dfasfda","nq":["search query"]}`, - `{"pid":"dfasfda","nq":["search query"],"subId":"any string value","category":"any string value"}`, -} - -var invalidParams = []string{ - `{"pid":123}`, - `{"pid":"12323","nq":"search query not an array"}`, - `{"pid":"12323","category":1}`, - `{"pid":"12323","subId":23}`, - ``, - `null`, - `true`, - `9`, - `1.2`, - `[]`, - `{}`, - `placementId`, - `zone`, - `zoneId`, -} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 3a2f871e199..c5b4b1134cc 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -118,7 +118,6 @@ import ( "github.com/prebid/prebid-server/adapters/mobfoxpb" "github.com/prebid/prebid-server/adapters/mobilefuse" "github.com/prebid/prebid-server/adapters/motorik" - "github.com/prebid/prebid-server/adapters/nanointeractive" "github.com/prebid/prebid-server/adapters/nextmillennium" "github.com/prebid/prebid-server/adapters/nobid" "github.com/prebid/prebid-server/adapters/onetag" @@ -317,7 +316,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderMobfoxpb: mobfoxpb.Builder, openrtb_ext.BidderMobileFuse: mobilefuse.Builder, openrtb_ext.BidderMotorik: motorik.Builder, - openrtb_ext.BidderNanoInteractive: nanointeractive.Builder, openrtb_ext.BidderNextMillennium: nextmillennium.Builder, openrtb_ext.BidderNoBid: nobid.Builder, openrtb_ext.BidderOneTag: onetag.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 8ce6f0df67c..2337f7f6bbb 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -109,22 +109,23 @@ func GetActiveBidders(infos config.BidderInfos) map[string]openrtb_ext.BidderNam func GetDisabledBidderWarningMessages(infos config.BidderInfos) map[string]string { removed := map[string]string{ - "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, - "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, - "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, - "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAds" in your configuration.`, - "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, - "oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`, - "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, - "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, - "brightroll": `Bidder "brightroll" is no longer available in Prebid Server. Please update your configuration.`, - "engagebdr": `Bidder "engagebdr" is no longer available in Prebid Server. Please update your configuration.`, - "yeahmobi": `Bidder "yeahmobi" is no longer available in Prebid Server. Please update your configuration.`, - "ninthdecimal": `Bidder "ninthdecimal" is no longer available in Prebid Server. Please update your configuration.`, - "kubient": `Bidder "kubient" is no longer available in Prebid Server. Please update your configuration.`, - "definemedia": `Bidder "definemedia" is no longer available in Prebid Server. Please update your configuration.`, - "applogy": `Bidder "applogy" is no longer available in Prebid Server. Please update your configuration.`, - "rhythmone": `Bidder "rhythmone" is no longer available in Prebid Server. Please update your configuration.`, + "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, + "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, + "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, + "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAds" in your configuration.`, + "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, + "oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`, + "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, + "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, + "brightroll": `Bidder "brightroll" is no longer available in Prebid Server. Please update your configuration.`, + "engagebdr": `Bidder "engagebdr" is no longer available in Prebid Server. Please update your configuration.`, + "yeahmobi": `Bidder "yeahmobi" is no longer available in Prebid Server. Please update your configuration.`, + "ninthdecimal": `Bidder "ninthdecimal" is no longer available in Prebid Server. Please update your configuration.`, + "kubient": `Bidder "kubient" is no longer available in Prebid Server. Please update your configuration.`, + "definemedia": `Bidder "definemedia" is no longer available in Prebid Server. Please update your configuration.`, + "applogy": `Bidder "applogy" is no longer available in Prebid Server. Please update your configuration.`, + "rhythmone": `Bidder "rhythmone" is no longer available in Prebid Server. Please update your configuration.`, + "nanointeractive": `Bidder "nanointeractive" is no longer available in Prebid Server. Please update your configuration.`, } return mergeRemovedAndDisabledBidderWarningMessages(removed, infos) diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 34c01f1ce32..67f43ec62dd 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -146,7 +146,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderMobfoxpb, BidderMobileFuse, BidderMotorik, - BidderNanoInteractive, BidderNextMillennium, BidderNoBid, BidderOneTag, @@ -435,7 +434,6 @@ const ( BidderMobfoxpb BidderName = "mobfoxpb" BidderMobileFuse BidderName = "mobilefuse" BidderMotorik BidderName = "motorik" - BidderNanoInteractive BidderName = "nanointeractive" BidderNextMillennium BidderName = "nextmillennium" BidderNoBid BidderName = "nobid" BidderOneTag BidderName = "onetag" diff --git a/openrtb_ext/imp_nanointeractive.go b/openrtb_ext/imp_nanointeractive.go deleted file mode 100644 index b381fab8eb7..00000000000 --- a/openrtb_ext/imp_nanointeractive.go +++ /dev/null @@ -1,10 +0,0 @@ -package openrtb_ext - -// ExtImpNanoInteractive defines the contract for bidrequest.imp[i].ext.prebid.bidder.nanointeractive -type ExtImpNanoInteractive struct { - Pid string `json:"pid"` - Nq []string `json:"nq,omitempty"` - Category string `json:"category,omitempty"` - SubId string `json:"subId,omitempty"` - Ref string `json:"ref,omitempty"` -} diff --git a/static/bidder-info/nanointeractive.yaml b/static/bidder-info/nanointeractive.yaml deleted file mode 100644 index 639c5450d2e..00000000000 --- a/static/bidder-info/nanointeractive.yaml +++ /dev/null @@ -1,15 +0,0 @@ -endpoint: "https://ad.audiencemanager.de/hbs" -maintainer: - email: "development@nanointeractive.com" -gvlVendorID: 72 -capabilities: - app: - mediaTypes: - - banner - site: - mediaTypes: - - banner -userSync: - redirect: - url: "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri={{.RedirectURL}}" - userMacro: "$UID" diff --git a/static/bidder-params/nanointeractive.json b/static/bidder-params/nanointeractive.json deleted file mode 100644 index aacd3154a92..00000000000 --- a/static/bidder-params/nanointeractive.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "NanoInteractive Adapter Params", - "description": "A schema which validates params accepted by the NanoInteractive adapter", - "type": "object", - "properties": { - "pid": { - "type": "string", - "description": "Placement id" - }, - "nq": { - "type": "array", - "items": { - "type": "string" - }, - "description": "search queries" - }, - "category": { - "type": "string", - "description": "IAB Category" - }, - "subId": { - "type": "string", - "description": "any segment value provided by publisher" - }, - "ref" : { - "type": "string", - "description": "referer" - } - }, - "required": ["pid"] -} From efdda05e3316c64d358898e6c1f55aba512a63bd Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 27 Sep 2023 09:09:41 -0400 Subject: [PATCH 020/138] Remove Config Backwards Compatibility: File System Stored Requests (#3124) --- config/config.go | 14 -------------- config/config_test.go | 27 --------------------------- 2 files changed, 41 deletions(-) diff --git a/config/config.go b/config/config.go index f503d0fcb59..dd7671c1412 100644 --- a/config/config.go +++ b/config/config.go @@ -1069,7 +1069,6 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.ReadInConfig() // Migrate config settings to maintain compatibility with old configs - migrateConfig(v) migrateConfigPurposeOneTreatment(v) migrateConfigSpecialFeature1(v) migrateConfigTCF2PurposeFlags(v) @@ -1135,19 +1134,6 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { } } -func migrateConfig(v *viper.Viper) { - // if stored_requests.filesystem is not a map in conf file as expected from defaults, - // means we have old-style settings; migrate them to new filesystem map to avoid breaking viper - if _, ok := v.Get("stored_requests.filesystem").(map[string]interface{}); !ok { - glog.Warning("stored_requests.filesystem should be changed to stored_requests.filesystem.enabled") - glog.Warning("stored_requests.directorypath should be changed to stored_requests.filesystem.directorypath") - m := v.GetStringMap("stored_requests.filesystem") - m["enabled"] = v.GetBool("stored_requests.filesystem") - m["directorypath"] = v.GetString("stored_requests.directorypath") - v.Set("stored_requests.filesystem", m) - } -} - func migrateConfigCompression(v *viper.Viper) { oldField := "enable_gzip" newField := "compression.response.enable_gzip" diff --git a/config/config_test.go b/config/config_test.go index 057ed06ecc4..325c59f6802 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -492,12 +492,6 @@ tmax_adjustments: pbs_response_preparation_duration_ms: 100 `) -var oldStoredRequestsConfig = []byte(` -stored_requests: - filesystem: true - directorypath: "/somepath" -`) - func cmpStrings(t *testing.T, key, expected, actual string) { t.Helper() assert.Equal(t, expected, actual, "%s: %s != %s", key, expected, actual) @@ -818,36 +812,15 @@ func TestValidateConfig(t *testing.T) { assert.Nil(t, err, "OpenRTB filesystem config should work. %v", err) } -func TestMigrateConfig(t *testing.T) { - v := viper.New() - SetupViper(v, "", bidderInfos) - v.Set("gdpr.default_value", "0") - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(oldStoredRequestsConfig)) - migrateConfig(v) - cfg, err := New(v, bidderInfos, mockNormalizeBidderName) - assert.NoError(t, err, "Setting up config should work but it doesn't") - cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) - cmpStrings(t, "stored_requests.filesystem.path", "/somepath", cfg.StoredRequests.Files.Path) -} - func TestMigrateConfigFromEnv(t *testing.T) { - if oldval, ok := os.LookupEnv("PBS_STORED_REQUESTS_FILESYSTEM"); ok { - defer os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", oldval) - } else { - defer os.Unsetenv("PBS_STORED_REQUESTS_FILESYSTEM") - } - if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_ENDPOINT"); ok { defer os.Setenv("PBS_ADAPTERS_BIDDER1_ENDPOINT", oldval) } else { defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_ENDPOINT") } - os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", "true") os.Setenv("PBS_ADAPTERS_BIDDER1_ENDPOINT", "http://bidder1_override.com") cfg, _ := newDefaultConfig(t) - cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) cmpStrings(t, "adapters.bidder1.endpoint", "http://bidder1_override.com", cfg.BidderInfos["bidder1"].Endpoint) } From 0c8f79b02890482e6e7d8a197807668ffc63ee4d Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:31:21 -0400 Subject: [PATCH 021/138] Remove Config Backwards Compatibility: Enable Events (#3135) --- account/account.go | 16 ---------- account/account_test.go | 64 --------------------------------------- config/account.go | 1 - config/config.go | 36 +++------------------- config/config_test.go | 57 ++-------------------------------- config/events.go | 9 ++---- config/events_test.go | 47 +++------------------------- endpoints/events/event.go | 2 +- exchange/events.go | 4 +-- exchange/exchange_test.go | 2 +- 10 files changed, 17 insertions(+), 221 deletions(-) diff --git a/account/account.go b/account/account.go index 156ad5e6d39..36b6da51c38 100644 --- a/account/account.go +++ b/account/account.go @@ -111,9 +111,6 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r account.Privacy.IPv4Config.AnonKeepBits = iputil.IPv4DefaultMaskingBitSize } - // set the value of events.enabled field based on deprecated events_enabled field and ensure backward compatibility - deprecateEventsEnabledField(account) - return account, nil } @@ -264,16 +261,3 @@ func useGDPRChannelEnabled(account *config.Account) bool { func useCCPAChannelEnabled(account *config.Account) bool { return account.CCPA.ChannelEnabled.IsSet() && !account.CCPA.IntegrationEnabled.IsSet() } - -// deprecateEventsEnabledField is responsible for ensuring backwards compatibility of "events_enabled" field. -// This function favors "events.enabled" field over deprecated "events_enabled" field, if values for both are set. -// If only deprecated "events_enabled" field is set then it sets the same value to "events.enabled" field. -func deprecateEventsEnabledField(account *config.Account) { - if account != nil { - if account.Events.Enabled == nil { - account.Events.Enabled = account.EventsEnabled - } - // assign the old value to the new value so old and new are always the same even though the new value is what is used in the application code. - account.EventsEnabled = account.Events.Enabled - } -} diff --git a/account/account_test.go b/account/account_test.go index d2694abc5a1..b527dee149f 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -13,7 +13,6 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -590,66 +589,3 @@ func TestAccountUpgradeStatusGetAccount(t *testing.T) { }) } } - -func TestDeprecateEventsEnabledField(t *testing.T) { - testCases := []struct { - name string - account *config.Account - want *bool - }{ - { - name: "account is nil", - account: nil, - want: nil, - }, - { - name: "account.EventsEnabled is nil, account.Events.Enabled is nil", - account: &config.Account{ - EventsEnabled: nil, - Events: config.Events{ - Enabled: nil, - }, - }, - want: nil, - }, - { - name: "account.EventsEnabled is nil, account.Events.Enabled is non-nil", - account: &config.Account{ - EventsEnabled: nil, - Events: config.Events{ - Enabled: ptrutil.ToPtr(true), - }, - }, - want: ptrutil.ToPtr(true), - }, - { - name: "account.EventsEnabled is non-nil, account.Events.Enabled is nil", - account: &config.Account{ - EventsEnabled: ptrutil.ToPtr(true), - Events: config.Events{ - Enabled: nil, - }, - }, - want: ptrutil.ToPtr(true), - }, - { - name: "account.EventsEnabled is non-nil, account.Events.Enabled is non-nil", - account: &config.Account{ - EventsEnabled: ptrutil.ToPtr(false), - Events: config.Events{ - Enabled: ptrutil.ToPtr(true), - }, - }, - want: ptrutil.ToPtr(true), - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - deprecateEventsEnabledField(test.account) - if test.account != nil { - assert.Equal(t, test.want, test.account.Events.Enabled) - } - }) - } -} diff --git a/config/account.go b/config/account.go index 4b0ceeef0cf..d57fdb90773 100644 --- a/config/account.go +++ b/config/account.go @@ -28,7 +28,6 @@ type Account struct { ID string `mapstructure:"id" json:"id"` Disabled bool `mapstructure:"disabled" json:"disabled"` CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"` - EventsEnabled *bool `mapstructure:"events_enabled" json:"events_enabled"` // Deprecated: Use events.enabled instead. CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"` GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"` DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"` diff --git a/config/config.go b/config/config.go index dd7671c1412..9c2ef700f75 100644 --- a/config/config.go +++ b/config/config.go @@ -15,7 +15,6 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" "github.com/spf13/viper" ) @@ -141,12 +140,12 @@ func (cfg *Configuration) validate(v *viper.Viper) []error { glog.Warning(`With account_defaults.disabled=true, host-defined accounts must exist and have "disabled":false. All other requests will be rejected.`) } - if cfg.PriceFloors.Enabled { - glog.Warning(`cfg.PriceFloors.Enabled will currently not do anything as price floors feature is still under development.`) + if cfg.AccountDefaults.Events.Enabled { + glog.Warning(`account_defaults.events has no effect as the feature is under development.`) } - if len(cfg.AccountDefaults.Events.VASTEvents) > 0 { - errs = append(errs, errors.New("account_defaults.Events.VASTEvents has no effect as the feature is under development.")) + if cfg.PriceFloors.Enabled { + glog.Warning(`cfg.PriceFloors.Enabled will currently not do anything as price floors feature is still under development.`) } errs = cfg.Experiment.validate(errs) @@ -695,9 +694,6 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin // Update account defaults and generate base json for patch c.AccountDefaults.CacheTTL = c.CacheURL.DefaultTTLs // comment this out to set explicitly in config - // Update the deprecated and new events enabled values for account defaults. - c.AccountDefaults.EventsEnabled, c.AccountDefaults.Events.Enabled = migrateConfigEventsEnabled(c.AccountDefaults.EventsEnabled, c.AccountDefaults.Events.Enabled) - if err := c.MarshalAccountDefaults(); err != nil { return nil, err } @@ -1019,7 +1015,6 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("account_defaults.price_floors.use_dynamic_data", false) v.SetDefault("account_defaults.price_floors.max_rules", 100) v.SetDefault("account_defaults.price_floors.max_schema_dims", 3) - v.SetDefault("account_defaults.events_enabled", false) v.SetDefault("account_defaults.privacy.ipv6.anon_keep_bits", 56) v.SetDefault("account_defaults.privacy.ipv4.anon_keep_bits", 24) @@ -1118,6 +1113,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { // Defaults for account_defaults.events.default_url v.SetDefault("account_defaults.events.default_url", "https://PBS_HOST/event?t=##PBS-EVENTTYPE##&vtype=##PBS-VASTEVENT##&b=##PBS-BIDID##&f=i&a=##PBS-ACCOUNTID##&ts=##PBS-TIMESTAMP##&bidder=##PBS-BIDDER##&int=##PBS-INTEGRATION##&mt=##PBS-MEDIATYPE##&ch=##PBS-CHANNEL##&aid=##PBS-AUCTIONID##&l=##PBS-LINEID##") + v.SetDefault("account_defaults.events.enabled", false) v.SetDefault("experiment.adscert.mode", "off") v.SetDefault("experiment.adscert.inprocess.origin", "") @@ -1396,28 +1392,6 @@ func migrateConfigDatabaseConnection(v *viper.Viper) { } } -// migrateConfigEventsEnabled is responsible for ensuring backward compatibility of events_enabled field. -// This function copies the value of newField "events.enabled" and set it to the oldField "events_enabled". -// This is necessary to achieve the desired order of precedence favoring the account values over the host values -// given the account fetcher JSON merge mechanics. -func migrateConfigEventsEnabled(oldFieldValue *bool, newFieldValue *bool) (updatedOldFieldValue, updatedNewFieldValue *bool) { - newField := "account_defaults.events.enabled" - oldField := "account_defaults.events_enabled" - - updatedOldFieldValue = oldFieldValue - if oldFieldValue != nil { - glog.Warningf("%s is deprecated and should be changed to %s", oldField, newField) - } - if newFieldValue != nil { - if oldFieldValue != nil { - glog.Warningf("using %s and ignoring deprecated %s", newField, oldField) - } - updatedOldFieldValue = ptrutil.ToPtr(*newFieldValue) - } - - return updatedOldFieldValue, nil -} - func isConfigInfoPresent(v *viper.Viper, prefix string, fields []string) bool { prefix = prefix + "." for _, field := range fields { diff --git a/config/config_test.go b/config/config_test.go index 325c59f6802..5cc6fd65f51 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -12,7 +12,6 @@ import ( "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" "github.com/spf13/viper" "github.com/stretchr/testify/assert" ) @@ -191,8 +190,7 @@ func TestDefaults(t *testing.T) { cmpBools(t, "account_defaults.price_floors.use_dynamic_data", false, cfg.AccountDefaults.PriceFloors.UseDynamicData) cmpInts(t, "account_defaults.price_floors.max_rules", 100, cfg.AccountDefaults.PriceFloors.MaxRule) cmpInts(t, "account_defaults.price_floors.max_schema_dims", 3, cfg.AccountDefaults.PriceFloors.MaxSchemaDims) - cmpBools(t, "account_defaults.events_enabled", *cfg.AccountDefaults.EventsEnabled, false) - cmpNils(t, "account_defaults.events.enabled", cfg.AccountDefaults.Events.Enabled) + cmpBools(t, "account_defaults.events.enabled", false, cfg.AccountDefaults.Events.Enabled) cmpBools(t, "hooks.enabled", false, cfg.Hooks.Enabled) cmpStrings(t, "validations.banner_creative_max_size", "skip", cfg.Validations.BannerCreativeMaxSize) @@ -469,7 +467,6 @@ hooks: price_floors: enabled: true account_defaults: - events_enabled: false events: enabled: true price_floors: @@ -582,8 +579,7 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "account_defaults.price_floors.use_dynamic_data", true, cfg.AccountDefaults.PriceFloors.UseDynamicData) cmpInts(t, "account_defaults.price_floors.max_rules", 120, cfg.AccountDefaults.PriceFloors.MaxRule) cmpInts(t, "account_defaults.price_floors.max_schema_dims", 5, cfg.AccountDefaults.PriceFloors.MaxSchemaDims) - cmpBools(t, "account_defaults.events_enabled", *cfg.AccountDefaults.EventsEnabled, true) - cmpNils(t, "account_defaults.events.enabled", cfg.AccountDefaults.Events.Enabled) + cmpBools(t, "account_defaults.events.enabled", true, cfg.AccountDefaults.Events.Enabled) cmpInts(t, "account_defaults.privacy.ipv6.anon_keep_bits", 50, cfg.AccountDefaults.Privacy.IPv6Config.AnonKeepBits) cmpInts(t, "account_defaults.privacy.ipv4.anon_keep_bits", 20, cfg.AccountDefaults.Privacy.IPv4Config.AnonKeepBits) @@ -3287,52 +3283,3 @@ func TestTCF2FeatureOneVendorException(t *testing.T) { assert.Equal(t, tt.wantIsVendorException, value, tt.description) } } - -func TestMigrateConfigEventsEnabled(t *testing.T) { - testCases := []struct { - name string - oldFieldValue *bool - newFieldValue *bool - expectedOldFieldValue *bool - expectedNewFieldValue *bool - }{ - { - name: "Both old and new fields are nil", - oldFieldValue: nil, - newFieldValue: nil, - expectedOldFieldValue: nil, - expectedNewFieldValue: nil, - }, - { - name: "Only old field is set", - oldFieldValue: ptrutil.ToPtr(true), - newFieldValue: nil, - expectedOldFieldValue: ptrutil.ToPtr(true), - expectedNewFieldValue: nil, - }, - { - name: "Only new field is set", - oldFieldValue: nil, - newFieldValue: ptrutil.ToPtr(true), - expectedOldFieldValue: ptrutil.ToPtr(true), - expectedNewFieldValue: nil, - }, - { - name: "Both old and new fields are set, override old field with new field value", - oldFieldValue: ptrutil.ToPtr(false), - newFieldValue: ptrutil.ToPtr(true), - expectedOldFieldValue: ptrutil.ToPtr(true), - expectedNewFieldValue: nil, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - updatedOldFieldValue, updatedNewFieldValue := migrateConfigEventsEnabled(tc.oldFieldValue, tc.newFieldValue) - - assert.Equal(t, tc.expectedOldFieldValue, updatedOldFieldValue) - assert.Nil(t, updatedNewFieldValue) - assert.Nil(t, tc.expectedNewFieldValue) - }) - } -} diff --git a/config/events.go b/config/events.go index 83d2df4b58d..cf3139d83ca 100644 --- a/config/events.go +++ b/config/events.go @@ -61,14 +61,14 @@ type VASTEvent struct { // within the VAST XML // Don't enable this feature. It is still under developmment. Please follow https://github.com/prebid/prebid-server/issues/1725 for more updates type Events struct { - Enabled *bool `mapstructure:"enabled" json:"enabled"` + Enabled bool `mapstructure:"enabled" json:"enabled"` DefaultURL string `mapstructure:"default_url" json:"default_url"` VASTEvents []VASTEvent `mapstructure:"vast_events" json:"vast_events,omitempty"` } // validate verifies the events object and returns error if at least one is invalid. func (e Events) validate(errs []error) []error { - if e.IsEnabled() { + if e.Enabled { if !isValidURL(e.DefaultURL) { return append(errs, errors.New("Invalid events.default_url")) } @@ -147,8 +147,3 @@ func isValidURL(eventURL string) bool { func (e VASTEvent) isTrackingEvent() bool { return e.CreateElement == TrackingVASTElement } - -// IsEnabled function returns the value of events.enabled field -func (e Events) IsEnabled() bool { - return e.Enabled != nil && *e.Enabled -} diff --git a/config/events_test.go b/config/events_test.go index 4baa9066dec..b4c196e9fa9 100644 --- a/config/events_test.go +++ b/config/events_test.go @@ -3,7 +3,6 @@ package config import ( "testing" - "github.com/prebid/prebid-server/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -262,29 +261,22 @@ func TestValidate(t *testing.T) { { description: "Empty default URL", events: Events{ - Enabled: ptrutil.ToPtr(true), + Enabled: true, }, expectErr: true, }, { description: "Events are disabled. Skips validations", events: Events{ - Enabled: ptrutil.ToPtr(false), + Enabled: false, DefaultURL: "", }, expectErr: false, }, - { - description: "Events are nil. Skip validations", - events: Events{ - Enabled: nil, - }, - expectErr: false, - }, { description: "No VAST Events and default URL present", events: Events{ - Enabled: ptrutil.ToPtr(true), + Enabled: true, DefaultURL: "http://prebid.org", }, expectErr: false, @@ -292,7 +284,7 @@ func TestValidate(t *testing.T) { { description: "Invalid VAST Event", events: Events{ - Enabled: ptrutil.ToPtr(true), + Enabled: true, DefaultURL: "http://prebid.org", VASTEvents: []VASTEvent{ {}, @@ -335,34 +327,3 @@ func TestValidateVASTEvents(t *testing.T) { assert.Equal(t, !test.expectErr, err == nil, test.description) } } - -func TestIsEnabled(t *testing.T) { - testCases := []struct { - name string - events Events - expected bool - }{ - { - name: "nil pointer", - events: Events{}, - expected: false, - }, - { - name: "event false", - events: Events{Enabled: ptrutil.ToPtr(false)}, - expected: false, - }, - { - name: "event true", - events: Events{Enabled: ptrutil.ToPtr(true)}, - expected: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - actual := tc.events.IsEnabled() - assert.Equal(t, tc.expected, actual) - }) - } -} diff --git a/endpoints/events/event.go b/endpoints/events/event.go index 089d5606552..e8084f1b273 100644 --- a/endpoints/events/event.go +++ b/endpoints/events/event.go @@ -108,7 +108,7 @@ func (e *eventEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprou } // Check if events are enabled for the account - if !account.Events.IsEnabled() { + if !account.Events.Enabled { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(fmt.Sprintf("Account '%s' doesn't support events", eventRequest.AccountID))) return diff --git a/exchange/events.go b/exchange/events.go index fd52d6b676a..e4f6b0d503f 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -28,7 +28,7 @@ type eventTracking struct { func getEventTracking(requestExtPrebid *openrtb_ext.ExtRequestPrebid, ts time.Time, account *config.Account, bidderInfos config.BidderInfos, externalURL string) *eventTracking { return &eventTracking{ accountID: account.ID, - enabledForAccount: account.Events.IsEnabled(), + enabledForAccount: account.Events.Enabled, enabledForRequest: requestExtPrebid != nil && requestExtPrebid.Events != nil, auctionTimestampMs: ts.UnixNano() / 1e+6, integrationType: getIntegrationType(requestExtPrebid), @@ -130,7 +130,7 @@ func (ev *eventTracking) makeEventURL(evType analytics.EventType, pbsBid *entiti }) } -// isEnabled checks if events are enabled by default or on account/request level +// isEventAllowed checks if events are enabled by default or on account/request level func (ev *eventTracking) isEventAllowed() bool { return ev.enabledForAccount || ev.enabledForRequest } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index c4b85ebeb1f..6e6e8afe76e 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2359,7 +2359,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { Account: config.Account{ ID: "testaccount", Events: config.Events{ - Enabled: &spec.EventsEnabled, + Enabled: spec.EventsEnabled, }, DebugAllow: true, PriceFloors: config.AccountPriceFloors{Enabled: spec.AccountFloorsEnabled}, From fc3694436c499e6c584bee946df46422cf432eef Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:31:03 -0400 Subject: [PATCH 022/138] Remove Config Backwards Compatibility: Enable GZIP (#3125) --- config/config.go | 18 ----------- config/config_test.go | 71 ------------------------------------------- server/server_test.go | 1 - 3 files changed, 90 deletions(-) diff --git a/config/config.go b/config/config.go index 9c2ef700f75..df4d53f1813 100644 --- a/config/config.go +++ b/config/config.go @@ -28,7 +28,6 @@ type Configuration struct { Client HTTPClient `mapstructure:"http_client"` CacheClient HTTPClient `mapstructure:"http_client_cache"` AdminPort int `mapstructure:"admin_port"` - EnableGzip bool `mapstructure:"enable_gzip"` Compression Compression `mapstructure:"compression"` // GarbageCollectorThreshold allocates virtual memory (in bytes) which is not used by PBS but // serves as a hack to trigger the garbage collector only when the heap reaches at least this size. @@ -1068,7 +1067,6 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { migrateConfigSpecialFeature1(v) migrateConfigTCF2PurposeFlags(v) migrateConfigDatabaseConnection(v) - migrateConfigCompression(v) // These defaults must be set after the migrate functions because those functions look for the presence of these // config fields and there isn't a way to detect presence of a config field using the viper package if a default @@ -1109,8 +1107,6 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("gdpr.tcf2.special_feature1.vendor_exceptions", []openrtb_ext.BidderName{}) v.SetDefault("price_floors.enabled", false) - v.SetDefault("enable_gzip", false) - // Defaults for account_defaults.events.default_url v.SetDefault("account_defaults.events.default_url", "https://PBS_HOST/event?t=##PBS-EVENTTYPE##&vtype=##PBS-VASTEVENT##&b=##PBS-BIDID##&f=i&a=##PBS-ACCOUNTID##&ts=##PBS-TIMESTAMP##&bidder=##PBS-BIDDER##&int=##PBS-INTEGRATION##&mt=##PBS-MEDIATYPE##&ch=##PBS-CHANNEL##&aid=##PBS-AUCTIONID##&l=##PBS-LINEID##") v.SetDefault("account_defaults.events.enabled", false) @@ -1130,20 +1126,6 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { } } -func migrateConfigCompression(v *viper.Viper) { - oldField := "enable_gzip" - newField := "compression.response.enable_gzip" - if v.IsSet(oldField) { - oldConfig := v.GetBool(oldField) - if v.IsSet(newField) { - glog.Warningf("using %s and ignoring deprecated %s", newField, oldField) - } else { - glog.Warningf("%s is deprecated and should be changed to %s", oldField, newField) - v.Set(newField, oldConfig) - } - } -} - func migrateConfigPurposeOneTreatment(v *viper.Viper) { if oldConfig, ok := v.Get("gdpr.tcf2.purpose_one_treatement").(map[string]interface{}); ok { if v.IsSet("gdpr.tcf2.purpose_one_treatment") { diff --git a/config/config_test.go b/config/config_test.go index 5cc6fd65f51..69bd12134da 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -179,7 +179,6 @@ func TestDefaults(t *testing.T) { cmpBools(t, "price_floors.enabled", false, cfg.PriceFloors.Enabled) // Assert compression related defaults - cmpBools(t, "enable_gzip", false, cfg.EnableGzip) cmpBools(t, "compression.request.enable_gzip", false, cfg.Compression.Request.GZIP) cmpBools(t, "compression.response.enable_gzip", false, cfg.Compression.Response.GZIP) @@ -384,7 +383,6 @@ external_url: http://prebid-server.prebid.org/ host: prebid-server.prebid.org port: 1234 admin_port: 5678 -enable_gzip: false compression: request: enable_gzip: true @@ -585,7 +583,6 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "account_defaults.privacy.ipv4.anon_keep_bits", 20, cfg.AccountDefaults.Privacy.IPv4Config.AnonKeepBits) // Assert compression related defaults - cmpBools(t, "enable_gzip", false, cfg.EnableGzip) cmpBools(t, "compression.request.enable_gzip", true, cfg.Compression.Request.GZIP) cmpBools(t, "compression.response.enable_gzip", false, cfg.Compression.Response.GZIP) @@ -2566,74 +2563,6 @@ func TestMigrateConfigDatabaseQueryParams(t *testing.T) { assert.Equal(t, want_queries.poll_for_updates_amp_query, v.GetString("stored_responses.database.poll_for_updates.amp_query")) } -func TestMigrateConfigCompression(t *testing.T) { - testCases := []struct { - desc string - config []byte - wantEnableGZIP bool - wantReqGZIPEnabled bool - wantRespGZIPEnabled bool - }{ - - { - desc: "New config and old config not set", - config: []byte{}, - wantEnableGZIP: false, - wantReqGZIPEnabled: false, - wantRespGZIPEnabled: false, - }, - { - desc: "Old config set, new config not set", - config: []byte(` - enable_gzip: true - `), - wantEnableGZIP: true, - wantRespGZIPEnabled: true, - wantReqGZIPEnabled: false, - }, - { - desc: "Old config not set, new config set", - config: []byte(` - compression: - response: - enable_gzip: true - request: - enable_gzip: false - `), - wantEnableGZIP: false, - wantRespGZIPEnabled: true, - wantReqGZIPEnabled: false, - }, - { - desc: "Old config set and new config set", - config: []byte(` - enable_gzip: true - compression: - response: - enable_gzip: false - request: - enable_gzip: true - `), - wantEnableGZIP: true, - wantRespGZIPEnabled: false, - wantReqGZIPEnabled: true, - }, - } - - for _, test := range testCases { - v := viper.New() - v.SetConfigType("yaml") - err := v.ReadConfig(bytes.NewBuffer(test.config)) - assert.NoError(t, err) - - migrateConfigCompression(v) - - assert.Equal(t, test.wantEnableGZIP, v.GetBool("enable_gzip"), test.desc) - assert.Equal(t, test.wantReqGZIPEnabled, v.GetBool("compression.request.enable_gzip"), test.desc) - assert.Equal(t, test.wantRespGZIPEnabled, v.GetBool("compression.response.enable_gzip"), test.desc) - } -} - func TestIsConfigInfoPresent(t *testing.T) { configPrefix1Field2Only := []byte(` prefix1: diff --git a/server/server_test.go b/server/server_test.go index 23b45656d7d..7af892d3567 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -192,7 +192,6 @@ func TestListen(t *testing.T) { Port: 8000, UnixSocketEnable: false, UnixSocketName: "prebid_socket", - EnableGzip: false, } ) From f879f63f62a8f722393cb9ae608a1c83df9349a3 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:04:33 -0400 Subject: [PATCH 023/138] Remove Config Backwards Compatibility: DB Connection Info (#3146) --- config/config.go | 281 +++----------- config/config_test.go | 875 ------------------------------------------ 2 files changed, 57 insertions(+), 1099 deletions(-) diff --git a/config/config.go b/config/config.go index df4d53f1813..b083719e956 100644 --- a/config/config.go +++ b/config/config.go @@ -889,6 +889,25 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("category_mapping.filesystem.enabled", true) v.SetDefault("category_mapping.filesystem.directorypath", "./static/category-mapping") v.SetDefault("category_mapping.http.endpoint", "") + v.SetDefault("stored_requests.database.connection.driver", "") + v.SetDefault("stored_requests.database.connection.dbname", "") + v.SetDefault("stored_requests.database.connection.host", "") + v.SetDefault("stored_requests.database.connection.port", 0) + v.SetDefault("stored_requests.database.connection.user", "") + v.SetDefault("stored_requests.database.connection.password", "") + v.SetDefault("stored_requests.database.connection.query_string", "") + v.SetDefault("stored_requests.database.connection.tls.root_cert", "") + v.SetDefault("stored_requests.database.connection.tls.client_cert", "") + v.SetDefault("stored_requests.database.connection.tls.client_key", "") + v.SetDefault("stored_requests.database.fetcher.query", "") + v.SetDefault("stored_requests.database.fetcher.amp_query", "") + v.SetDefault("stored_requests.database.initialize_caches.timeout_ms", 0) + v.SetDefault("stored_requests.database.initialize_caches.query", "") + v.SetDefault("stored_requests.database.initialize_caches.amp_query", "") + v.SetDefault("stored_requests.database.poll_for_updates.refresh_rate_seconds", 0) + v.SetDefault("stored_requests.database.poll_for_updates.timeout_ms", 0) + v.SetDefault("stored_requests.database.poll_for_updates.query", "") + v.SetDefault("stored_requests.database.poll_for_updates.amp_query", "") v.SetDefault("stored_requests.filesystem.enabled", false) v.SetDefault("stored_requests.filesystem.directorypath", "./stored_requests/data/by_id") v.SetDefault("stored_requests.directorypath", "./stored_requests/data/by_id") @@ -906,6 +925,25 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("stored_requests.http_events.timeout_ms", 0) // stored_video is short for stored_video_requests. // PBS is not in the business of storing video content beyond the normal prebid cache system. + v.SetDefault("stored_video_req.database.connection.driver", "") + v.SetDefault("stored_video_req.database.connection.dbname", "") + v.SetDefault("stored_video_req.database.connection.host", "") + v.SetDefault("stored_video_req.database.connection.port", 0) + v.SetDefault("stored_video_req.database.connection.user", "") + v.SetDefault("stored_video_req.database.connection.password", "") + v.SetDefault("stored_video_req.database.connection.query_string", "") + v.SetDefault("stored_video_req.database.connection.tls.root_cert", "") + v.SetDefault("stored_video_req.database.connection.tls.client_cert", "") + v.SetDefault("stored_video_req.database.connection.tls.client_key", "") + v.SetDefault("stored_video_req.database.fetcher.query", "") + v.SetDefault("stored_video_req.database.fetcher.amp_query", "") + v.SetDefault("stored_video_req.database.initialize_caches.timeout_ms", 0) + v.SetDefault("stored_video_req.database.initialize_caches.query", "") + v.SetDefault("stored_video_req.database.initialize_caches.amp_query", "") + v.SetDefault("stored_video_req.database.poll_for_updates.refresh_rate_seconds", 0) + v.SetDefault("stored_video_req.database.poll_for_updates.timeout_ms", 0) + v.SetDefault("stored_video_req.database.poll_for_updates.query", "") + v.SetDefault("stored_video_req.database.poll_for_updates.amp_query", "") v.SetDefault("stored_video_req.filesystem.enabled", false) v.SetDefault("stored_video_req.filesystem.directorypath", "") v.SetDefault("stored_video_req.http.endpoint", "") @@ -919,6 +957,25 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("stored_video_req.http_events.endpoint", "") v.SetDefault("stored_video_req.http_events.refresh_rate_seconds", 0) v.SetDefault("stored_video_req.http_events.timeout_ms", 0) + v.SetDefault("stored_responses.database.connection.driver", "") + v.SetDefault("stored_responses.database.connection.dbname", "") + v.SetDefault("stored_responses.database.connection.host", "") + v.SetDefault("stored_responses.database.connection.port", 0) + v.SetDefault("stored_responses.database.connection.user", "") + v.SetDefault("stored_responses.database.connection.password", "") + v.SetDefault("stored_responses.database.connection.query_string", "") + v.SetDefault("stored_responses.database.connection.tls.root_cert", "") + v.SetDefault("stored_responses.database.connection.tls.client_cert", "") + v.SetDefault("stored_responses.database.connection.tls.client_key", "") + v.SetDefault("stored_responses.database.fetcher.query", "") + v.SetDefault("stored_responses.database.fetcher.amp_query", "") + v.SetDefault("stored_responses.database.initialize_caches.timeout_ms", 0) + v.SetDefault("stored_responses.database.initialize_caches.query", "") + v.SetDefault("stored_responses.database.initialize_caches.amp_query", "") + v.SetDefault("stored_responses.database.poll_for_updates.refresh_rate_seconds", 0) + v.SetDefault("stored_responses.database.poll_for_updates.timeout_ms", 0) + v.SetDefault("stored_responses.database.poll_for_updates.query", "") + v.SetDefault("stored_responses.database.poll_for_updates.amp_query", "") v.SetDefault("stored_responses.filesystem.enabled", false) v.SetDefault("stored_responses.filesystem.directorypath", "") v.SetDefault("stored_responses.http.endpoint", "") @@ -1053,8 +1110,6 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("request_validation.ipv4_private_networks", []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16", "127.0.0.0/8"}) v.SetDefault("request_validation.ipv6_private_networks", []string{"::1/128", "fc00::/7", "fe80::/10", "ff00::/8", "2001:db8::/32"}) - bindDatabaseEnvVars(v) - // Set environment variable support: v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.SetTypeByDefaultValue(true) @@ -1066,7 +1121,6 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { migrateConfigPurposeOneTreatment(v) migrateConfigSpecialFeature1(v) migrateConfigTCF2PurposeFlags(v) - migrateConfigDatabaseConnection(v) // These defaults must be set after the migrate functions because those functions look for the presence of these // config fields and there isn't a way to detect presence of a config field using the viper package if a default @@ -1207,173 +1261,6 @@ func migrateConfigTCF2PurposeEnabledFlags(v *viper.Viper) { } } -func migrateConfigDatabaseConnection(v *viper.Viper) { - - type QueryParamMigration struct { - old string - new string - } - - type QueryMigration struct { - name string - params []QueryParamMigration - } - - type Migration struct { - old string - new string - fields []string - queryMigrations []QueryMigration - } - - queryParamMigrations := struct { - RequestIdList QueryParamMigration - ImpIdList QueryParamMigration - IdList QueryParamMigration - LastUpdated QueryParamMigration - }{ - RequestIdList: QueryParamMigration{ - old: "%REQUEST_ID_LIST%", - new: "$REQUEST_ID_LIST", - }, - ImpIdList: QueryParamMigration{ - old: "%IMP_ID_LIST%", - new: "$IMP_ID_LIST", - }, - IdList: QueryParamMigration{ - old: "%ID_LIST%", - new: "$ID_LIST", - }, - LastUpdated: QueryParamMigration{ - old: "$1", - new: "$LAST_UPDATED", - }, - } - - queryMigrations := []QueryMigration{ - { - name: "fetcher.query", - params: []QueryParamMigration{queryParamMigrations.RequestIdList, queryParamMigrations.ImpIdList, queryParamMigrations.IdList}, - }, - { - name: "fetcher.amp_query", - params: []QueryParamMigration{queryParamMigrations.RequestIdList, queryParamMigrations.ImpIdList, queryParamMigrations.IdList}, - }, - { - name: "poll_for_updates.query", - params: []QueryParamMigration{queryParamMigrations.LastUpdated}, - }, - { - name: "poll_for_updates.amp_query", - params: []QueryParamMigration{queryParamMigrations.LastUpdated}, - }, - } - - migrations := []Migration{ - { - old: "stored_requests.postgres", - new: "stored_requests.database", - fields: []string{ - "connection.dbname", - "connection.host", - "connection.port", - "connection.user", - "connection.password", - "fetcher.query", - "fetcher.amp_query", - "initialize_caches.timeout_ms", - "initialize_caches.query", - "initialize_caches.amp_query", - "poll_for_updates.refresh_rate_seconds", - "poll_for_updates.timeout_ms", - "poll_for_updates.query", - "poll_for_updates.amp_query", - }, - queryMigrations: queryMigrations, - }, - { - old: "stored_video_req.postgres", - new: "stored_video_req.database", - fields: []string{ - "connection.dbname", - "connection.host", - "connection.port", - "connection.user", - "connection.password", - "fetcher.query", - "initialize_caches.timeout_ms", - "initialize_caches.query", - "poll_for_updates.refresh_rate_seconds", - "poll_for_updates.timeout_ms", - "poll_for_updates.query", - }, - queryMigrations: queryMigrations, - }, - { - old: "stored_responses.postgres", - new: "stored_responses.database", - fields: []string{ - "connection.dbname", - "connection.host", - "connection.port", - "connection.user", - "connection.password", - "fetcher.query", - "initialize_caches.timeout_ms", - "initialize_caches.query", - "poll_for_updates.refresh_rate_seconds", - "poll_for_updates.timeout_ms", - "poll_for_updates.query", - }, - queryMigrations: queryMigrations, - }, - } - - for _, migration := range migrations { - driverField := migration.new + ".connection.driver" - newConfigInfoPresent := isConfigInfoPresent(v, migration.new, migration.fields) - oldConfigInfoPresent := isConfigInfoPresent(v, migration.old, migration.fields) - - if !newConfigInfoPresent && oldConfigInfoPresent { - glog.Warning(fmt.Sprintf("%s is deprecated and should be changed to %s", migration.old, migration.new)) - glog.Warning(fmt.Sprintf("%s is not set, using default (postgres)", driverField)) - v.Set(driverField, "postgres") - - for _, field := range migration.fields { - oldField := migration.old + "." + field - newField := migration.new + "." + field - if v.IsSet(oldField) { - glog.Warning(fmt.Sprintf("%s is deprecated and should be changed to %s", oldField, newField)) - v.Set(newField, v.Get(oldField)) - } - } - - for _, queryMigration := range migration.queryMigrations { - oldQueryField := migration.old + "." + queryMigration.name - newQueryField := migration.new + "." + queryMigration.name - queryString := v.GetString(oldQueryField) - for _, queryParam := range queryMigration.params { - if strings.Contains(queryString, queryParam.old) { - glog.Warning(fmt.Sprintf("Query param %s for %s is deprecated and should be changed to %s", queryParam.old, oldQueryField, queryParam.new)) - queryString = strings.ReplaceAll(queryString, queryParam.old, queryParam.new) - v.Set(newQueryField, queryString) - } - } - } - } else if newConfigInfoPresent && oldConfigInfoPresent { - glog.Warning(fmt.Sprintf("using %s and ignoring deprecated %s", migration.new, migration.old)) - - for _, field := range migration.fields { - oldField := migration.old + "." + field - newField := migration.new + "." + field - if v.IsSet(oldField) { - glog.Warning(fmt.Sprintf("using %s and ignoring deprecated %s", newField, oldField)) - } - } - } - } -} - func isConfigInfoPresent(v *viper.Viper, prefix string, fields []string) bool { prefix = prefix + "." for _, field := range fields { @@ -1385,60 +1272,6 @@ func isConfigInfoPresent(v *viper.Viper, prefix string, fields []string) bool { return false } -func bindDatabaseEnvVars(v *viper.Viper) { - v.BindEnv("stored_requests.database.connection.driver") - v.BindEnv("stored_requests.database.connection.dbname") - v.BindEnv("stored_requests.database.connection.host") - v.BindEnv("stored_requests.database.connection.port") - v.BindEnv("stored_requests.database.connection.user") - v.BindEnv("stored_requests.database.connection.password") - v.BindEnv("stored_requests.database.connection.query_string") - v.BindEnv("stored_requests.database.connection.tls.root_cert") - v.BindEnv("stored_requests.database.connection.tls.client_cert") - v.BindEnv("stored_requests.database.connection.tls.client_key") - v.BindEnv("stored_requests.database.fetcher.query") - v.BindEnv("stored_requests.database.fetcher.amp_query") - v.BindEnv("stored_requests.database.initialize_caches.timeout_ms") - v.BindEnv("stored_requests.database.initialize_caches.query") - v.BindEnv("stored_requests.database.initialize_caches.amp_query") - v.BindEnv("stored_requests.database.poll_for_updates.refresh_rate_seconds") - v.BindEnv("stored_requests.database.poll_for_updates.timeout_ms") - v.BindEnv("stored_requests.database.poll_for_updates.query") - v.BindEnv("stored_requests.database.poll_for_updates.amp_query") - v.BindEnv("stored_video_req.database.connection.driver") - v.BindEnv("stored_video_req.database.connection.dbname") - v.BindEnv("stored_video_req.database.connection.host") - v.BindEnv("stored_video_req.database.connection.port") - v.BindEnv("stored_video_req.database.connection.user") - v.BindEnv("stored_video_req.database.connection.password") - v.BindEnv("stored_video_req.database.connection.query_string") - v.BindEnv("stored_video_req.database.connection.tls.root_cert") - v.BindEnv("stored_video_req.database.connection.tls.client_cert") - v.BindEnv("stored_video_req.database.connection.tls.client_key") - v.BindEnv("stored_video_req.database.fetcher.query") - v.BindEnv("stored_video_req.database.initialize_caches.timeout_ms") - v.BindEnv("stored_video_req.database.initialize_caches.query") - v.BindEnv("stored_video_req.database.poll_for_updates.refresh_rate_seconds") - v.BindEnv("stored_video_req.database.poll_for_updates.timeout_ms") - v.BindEnv("stored_video_req.database.poll_for_updates.query") - v.BindEnv("stored_responses.database.connection.driver") - v.BindEnv("stored_responses.database.connection.dbname") - v.BindEnv("stored_responses.database.connection.host") - v.BindEnv("stored_responses.database.connection.port") - v.BindEnv("stored_responses.database.connection.user") - v.BindEnv("stored_responses.database.connection.password") - v.BindEnv("stored_responses.database.connection.query_string") - v.BindEnv("stored_responses.database.connection.tls.root_cert") - v.BindEnv("stored_responses.database.connection.tls.client_cert") - v.BindEnv("stored_responses.database.connection.tls.client_key") - v.BindEnv("stored_responses.database.fetcher.query") - v.BindEnv("stored_responses.database.initialize_caches.timeout_ms") - v.BindEnv("stored_responses.database.initialize_caches.query") - v.BindEnv("stored_responses.database.poll_for_updates.refresh_rate_seconds") - v.BindEnv("stored_responses.database.poll_for_updates.timeout_ms") - v.BindEnv("stored_responses.database.poll_for_updates.query") -} - func setBidderDefaults(v *viper.Viper, bidder string) { adapterCfgPrefix := "adapters." + bidder v.BindEnv(adapterCfgPrefix + ".disabled") diff --git a/config/config_test.go b/config/config_test.go index 69bd12134da..9fc85715c6a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,8 +5,6 @@ import ( "errors" "net" "os" - "strconv" - "strings" "testing" "time" @@ -1690,879 +1688,6 @@ func TestMigrateConfigTCF2EnforcePurposeFlags(t *testing.T) { } } -func TestMigrateConfigDatabaseConnection(t *testing.T) { - type configs struct { - old []byte - new []byte - both []byte - } - - // Stored Requests Config Migration - storedReqestsConfigs := configs{ - old: []byte(` - stored_requests: - postgres: - connection: - dbname: "old_connection_dbname" - host: "old_connection_host" - port: 1000 - user: "old_connection_user" - password: "old_connection_password" - fetcher: - query: "old_fetcher_query" - amp_query: "old_fetcher_amp_query" - initialize_caches: - timeout_ms: 1000 - query: "old_initialize_caches_query" - amp_query: "old_initialize_caches_amp_query" - poll_for_updates: - refresh_rate_seconds: 1000 - timeout_ms: 1000 - query: "old_poll_for_updates_query" - amp_query: "old_poll_for_updates_amp_query" - `), - new: []byte(` - stored_requests: - database: - connection: - dbname: "new_connection_dbname" - host: "new_connection_host" - port: 2000 - user: "new_connection_user" - password: "new_connection_password" - fetcher: - query: "new_fetcher_query" - amp_query: "new_fetcher_amp_query" - initialize_caches: - timeout_ms: 2000 - query: "new_initialize_caches_query" - amp_query: "new_initialize_caches_amp_query" - poll_for_updates: - refresh_rate_seconds: 2000 - timeout_ms: 2000 - query: "new_poll_for_updates_query" - amp_query: "new_poll_for_updates_amp_query" - `), - both: []byte(` - stored_requests: - postgres: - connection: - dbname: "old_connection_dbname" - host: "old_connection_host" - port: 1000 - user: "old_connection_user" - password: "old_connection_password" - fetcher: - query: "old_fetcher_query" - amp_query: "old_fetcher_amp_query" - initialize_caches: - timeout_ms: 1000 - query: "old_initialize_caches_query" - amp_query: "old_initialize_caches_amp_query" - poll_for_updates: - refresh_rate_seconds: 1000 - timeout_ms: 1000 - query: "old_poll_for_updates_query" - amp_query: "old_poll_for_updates_amp_query" - database: - connection: - dbname: "new_connection_dbname" - host: "new_connection_host" - port: 2000 - user: "new_connection_user" - password: "new_connection_password" - fetcher: - query: "new_fetcher_query" - amp_query: "new_fetcher_amp_query" - initialize_caches: - timeout_ms: 2000 - query: "new_initialize_caches_query" - amp_query: "new_initialize_caches_amp_query" - poll_for_updates: - refresh_rate_seconds: 2000 - timeout_ms: 2000 - query: "new_poll_for_updates_query" - amp_query: "new_poll_for_updates_amp_query" - `), - } - - storedRequestsTests := []struct { - description string - config []byte - - want_connection_dbname string - want_connection_host string - want_connection_port int - want_connection_user string - want_connection_password string - want_fetcher_query string - want_fetcher_amp_query string - want_initialize_caches_timeout_ms int - want_initialize_caches_query string - want_initialize_caches_amp_query string - want_poll_for_updates_refresh_rate_seconds int - want_poll_for_updates_timeout_ms int - want_poll_for_updates_query string - want_poll_for_updates_amp_query string - }{ - { - description: "New config and old config not set", - config: []byte{}, - }, - { - description: "New config not set, old config set", - config: storedReqestsConfigs.old, - - want_connection_dbname: "old_connection_dbname", - want_connection_host: "old_connection_host", - want_connection_port: 1000, - want_connection_user: "old_connection_user", - want_connection_password: "old_connection_password", - want_fetcher_query: "old_fetcher_query", - want_fetcher_amp_query: "old_fetcher_amp_query", - want_initialize_caches_timeout_ms: 1000, - want_initialize_caches_query: "old_initialize_caches_query", - want_initialize_caches_amp_query: "old_initialize_caches_amp_query", - want_poll_for_updates_refresh_rate_seconds: 1000, - want_poll_for_updates_timeout_ms: 1000, - want_poll_for_updates_query: "old_poll_for_updates_query", - want_poll_for_updates_amp_query: "old_poll_for_updates_amp_query", - }, - { - description: "New config set, old config not set", - config: storedReqestsConfigs.new, - - want_connection_dbname: "new_connection_dbname", - want_connection_host: "new_connection_host", - want_connection_port: 2000, - want_connection_user: "new_connection_user", - want_connection_password: "new_connection_password", - want_fetcher_query: "new_fetcher_query", - want_fetcher_amp_query: "new_fetcher_amp_query", - want_initialize_caches_timeout_ms: 2000, - want_initialize_caches_query: "new_initialize_caches_query", - want_initialize_caches_amp_query: "new_initialize_caches_amp_query", - want_poll_for_updates_refresh_rate_seconds: 2000, - want_poll_for_updates_timeout_ms: 2000, - want_poll_for_updates_query: "new_poll_for_updates_query", - want_poll_for_updates_amp_query: "new_poll_for_updates_amp_query", - }, - { - description: "New config and old config set", - config: storedReqestsConfigs.both, - - want_connection_dbname: "new_connection_dbname", - want_connection_host: "new_connection_host", - want_connection_port: 2000, - want_connection_user: "new_connection_user", - want_connection_password: "new_connection_password", - want_fetcher_query: "new_fetcher_query", - want_fetcher_amp_query: "new_fetcher_amp_query", - want_initialize_caches_timeout_ms: 2000, - want_initialize_caches_query: "new_initialize_caches_query", - want_initialize_caches_amp_query: "new_initialize_caches_amp_query", - want_poll_for_updates_refresh_rate_seconds: 2000, - want_poll_for_updates_timeout_ms: 2000, - want_poll_for_updates_query: "new_poll_for_updates_query", - want_poll_for_updates_amp_query: "new_poll_for_updates_amp_query", - }, - } - - for _, tt := range storedRequestsTests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigDatabaseConnection(v) - - if len(tt.config) > 0 { - assert.Equal(t, tt.want_connection_dbname, v.GetString("stored_requests.database.connection.dbname"), tt.description) - assert.Equal(t, tt.want_connection_host, v.GetString("stored_requests.database.connection.host"), tt.description) - assert.Equal(t, tt.want_connection_port, v.GetInt("stored_requests.database.connection.port"), tt.description) - assert.Equal(t, tt.want_connection_user, v.GetString("stored_requests.database.connection.user"), tt.description) - assert.Equal(t, tt.want_connection_password, v.GetString("stored_requests.database.connection.password"), tt.description) - assert.Equal(t, tt.want_fetcher_query, v.GetString("stored_requests.database.fetcher.query"), tt.description) - assert.Equal(t, tt.want_fetcher_amp_query, v.GetString("stored_requests.database.fetcher.amp_query"), tt.description) - assert.Equal(t, tt.want_initialize_caches_timeout_ms, v.GetInt("stored_requests.database.initialize_caches.timeout_ms"), tt.description) - assert.Equal(t, tt.want_initialize_caches_query, v.GetString("stored_requests.database.initialize_caches.query"), tt.description) - assert.Equal(t, tt.want_initialize_caches_amp_query, v.GetString("stored_requests.database.initialize_caches.amp_query"), tt.description) - assert.Equal(t, tt.want_poll_for_updates_refresh_rate_seconds, v.GetInt("stored_requests.database.poll_for_updates.refresh_rate_seconds"), tt.description) - assert.Equal(t, tt.want_poll_for_updates_timeout_ms, v.GetInt("stored_requests.database.poll_for_updates.timeout_ms"), tt.description) - assert.Equal(t, tt.want_poll_for_updates_query, v.GetString("stored_requests.database.poll_for_updates.query"), tt.description) - assert.Equal(t, tt.want_poll_for_updates_amp_query, v.GetString("stored_requests.database.poll_for_updates.amp_query"), tt.description) - } else { - assert.Nil(t, v.Get("stored_requests.database.connection.dbname"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.connection.host"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.connection.port"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.connection.user"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.connection.password"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.fetcher.query"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.fetcher.amp_query"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.initialize_caches.timeout_ms"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.initialize_caches.query"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.initialize_caches.amp_query"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.poll_for_updates.refresh_rate_seconds"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.poll_for_updates.timeout_ms"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.poll_for_updates.query"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.poll_for_updates.amp_query"), tt.description) - } - } - - // Stored Video Reqs Config Migration - storedVideoReqsConfigs := configs{ - old: []byte(` - stored_video_req: - postgres: - connection: - dbname: "old_connection_dbname" - host: "old_connection_host" - port: 1000 - user: "old_connection_user" - password: "old_connection_password" - fetcher: - query: "old_fetcher_query" - initialize_caches: - timeout_ms: 1000 - query: "old_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 1000 - timeout_ms: 1000 - query: "old_poll_for_updates_query" - `), - new: []byte(` - stored_video_req: - database: - connection: - dbname: "new_connection_dbname" - host: "new_connection_host" - port: 2000 - user: "new_connection_user" - password: "new_connection_password" - fetcher: - query: "new_fetcher_query" - initialize_caches: - timeout_ms: 2000 - query: "new_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 2000 - timeout_ms: 2000 - query: "new_poll_for_updates_query" - `), - both: []byte(` - stored_video_req: - postgres: - connection: - dbname: "old_connection_dbname" - host: "old_connection_host" - port: 1000 - user: "old_connection_user" - password: "old_connection_password" - fetcher: - query: "old_fetcher_query" - initialize_caches: - timeout_ms: 1000 - query: "old_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 1000 - timeout_ms: 1000 - query: "old_poll_for_updates_query" - database: - connection: - dbname: "new_connection_dbname" - host: "new_connection_host" - port: 2000 - user: "new_connection_user" - password: "new_connection_password" - fetcher: - query: "new_fetcher_query" - initialize_caches: - timeout_ms: 2000 - query: "new_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 2000 - timeout_ms: 2000 - query: "new_poll_for_updates_query" - `), - } - - storedVideoReqsTests := []struct { - description string - config []byte - - want_connection_dbname string - want_connection_host string - want_connection_port int - want_connection_user string - want_connection_password string - want_fetcher_query string - want_initialize_caches_timeout_ms int - want_initialize_caches_query string - want_poll_for_updates_refresh_rate_seconds int - want_poll_for_updates_timeout_ms int - want_poll_for_updates_query string - }{ - { - description: "New config and old config not set", - config: []byte{}, - }, - { - description: "New config not set, old config set", - config: storedVideoReqsConfigs.old, - - want_connection_dbname: "old_connection_dbname", - want_connection_host: "old_connection_host", - want_connection_port: 1000, - want_connection_user: "old_connection_user", - want_connection_password: "old_connection_password", - want_fetcher_query: "old_fetcher_query", - want_initialize_caches_timeout_ms: 1000, - want_initialize_caches_query: "old_initialize_caches_query", - want_poll_for_updates_refresh_rate_seconds: 1000, - want_poll_for_updates_timeout_ms: 1000, - want_poll_for_updates_query: "old_poll_for_updates_query", - }, - { - description: "New config set, old config not set", - config: storedVideoReqsConfigs.new, - - want_connection_dbname: "new_connection_dbname", - want_connection_host: "new_connection_host", - want_connection_port: 2000, - want_connection_user: "new_connection_user", - want_connection_password: "new_connection_password", - want_fetcher_query: "new_fetcher_query", - want_initialize_caches_timeout_ms: 2000, - want_initialize_caches_query: "new_initialize_caches_query", - want_poll_for_updates_refresh_rate_seconds: 2000, - want_poll_for_updates_timeout_ms: 2000, - want_poll_for_updates_query: "new_poll_for_updates_query", - }, - { - description: "New config and old config set", - config: storedVideoReqsConfigs.both, - - want_connection_dbname: "new_connection_dbname", - want_connection_host: "new_connection_host", - want_connection_port: 2000, - want_connection_user: "new_connection_user", - want_connection_password: "new_connection_password", - want_fetcher_query: "new_fetcher_query", - want_initialize_caches_timeout_ms: 2000, - want_initialize_caches_query: "new_initialize_caches_query", - want_poll_for_updates_refresh_rate_seconds: 2000, - want_poll_for_updates_timeout_ms: 2000, - want_poll_for_updates_query: "new_poll_for_updates_query", - }, - } - - for _, tt := range storedVideoReqsTests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigDatabaseConnection(v) - - if len(tt.config) > 0 { - assert.Equal(t, tt.want_connection_dbname, v.Get("stored_video_req.database.connection.dbname").(string), tt.description) - assert.Equal(t, tt.want_connection_host, v.Get("stored_video_req.database.connection.host").(string), tt.description) - assert.Equal(t, tt.want_connection_port, v.Get("stored_video_req.database.connection.port").(int), tt.description) - assert.Equal(t, tt.want_connection_user, v.Get("stored_video_req.database.connection.user").(string), tt.description) - assert.Equal(t, tt.want_connection_password, v.Get("stored_video_req.database.connection.password").(string), tt.description) - assert.Equal(t, tt.want_fetcher_query, v.Get("stored_video_req.database.fetcher.query").(string), tt.description) - assert.Equal(t, tt.want_initialize_caches_timeout_ms, v.Get("stored_video_req.database.initialize_caches.timeout_ms").(int), tt.description) - assert.Equal(t, tt.want_initialize_caches_query, v.Get("stored_video_req.database.initialize_caches.query").(string), tt.description) - assert.Equal(t, tt.want_poll_for_updates_refresh_rate_seconds, v.Get("stored_video_req.database.poll_for_updates.refresh_rate_seconds").(int), tt.description) - assert.Equal(t, tt.want_poll_for_updates_timeout_ms, v.Get("stored_video_req.database.poll_for_updates.timeout_ms").(int), tt.description) - assert.Equal(t, tt.want_poll_for_updates_query, v.Get("stored_video_req.database.poll_for_updates.query").(string), tt.description) - } else { - assert.Nil(t, v.Get("stored_video_req.database.connection.dbname"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.connection.host"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.connection.port"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.connection.user"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.connection.password"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.fetcher.query"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.initialize_caches.timeout_ms"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.initialize_caches.query"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.poll_for_updates.refresh_rate_seconds"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.poll_for_updates.timeout_ms"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.poll_for_updates.query"), tt.description) - } - } - - // Stored Responses Config Migration - storedResponsesConfigs := configs{ - old: []byte(` - stored_responses: - postgres: - connection: - dbname: "old_connection_dbname" - host: "old_connection_host" - port: 1000 - user: "old_connection_user" - password: "old_connection_password" - fetcher: - query: "old_fetcher_query" - initialize_caches: - timeout_ms: 1000 - query: "old_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 1000 - timeout_ms: 1000 - query: "old_poll_for_updates_query" - `), - new: []byte(` - stored_responses: - database: - connection: - dbname: "new_connection_dbname" - host: "new_connection_host" - port: 2000 - user: "new_connection_user" - password: "new_connection_password" - fetcher: - query: "new_fetcher_query" - initialize_caches: - timeout_ms: 2000 - query: "new_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 2000 - timeout_ms: 2000 - query: "new_poll_for_updates_query" - `), - both: []byte(` - stored_responses: - postgres: - connection: - dbname: "old_connection_dbname" - host: "old_connection_host" - port: 1000 - user: "old_connection_user" - password: "old_connection_password" - fetcher: - query: "old_fetcher_query" - initialize_caches: - timeout_ms: 1000 - query: "old_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 1000 - timeout_ms: 1000 - query: "old_poll_for_updates_query" - database: - connection: - dbname: "new_connection_dbname" - host: "new_connection_host" - port: 2000 - user: "new_connection_user" - password: "new_connection_password" - fetcher: - query: "new_fetcher_query" - initialize_caches: - timeout_ms: 2000 - query: "new_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 2000 - timeout_ms: 2000 - query: "new_poll_for_updates_query" - `), - } - - storedResponsesTests := []struct { - description string - config []byte - - want_connection_dbname string - want_connection_host string - want_connection_port int - want_connection_user string - want_connection_password string - want_fetcher_query string - want_initialize_caches_timeout_ms int - want_initialize_caches_query string - want_poll_for_updates_refresh_rate_seconds int - want_poll_for_updates_timeout_ms int - want_poll_for_updates_query string - }{ - { - description: "New config and old config not set", - config: []byte{}, - }, - { - description: "New config not set, old config set", - config: storedResponsesConfigs.old, - - want_connection_dbname: "old_connection_dbname", - want_connection_host: "old_connection_host", - want_connection_port: 1000, - want_connection_user: "old_connection_user", - want_connection_password: "old_connection_password", - want_fetcher_query: "old_fetcher_query", - want_initialize_caches_timeout_ms: 1000, - want_initialize_caches_query: "old_initialize_caches_query", - want_poll_for_updates_refresh_rate_seconds: 1000, - want_poll_for_updates_timeout_ms: 1000, - want_poll_for_updates_query: "old_poll_for_updates_query", - }, - { - description: "New config set, old config not set", - config: storedResponsesConfigs.new, - - want_connection_dbname: "new_connection_dbname", - want_connection_host: "new_connection_host", - want_connection_port: 2000, - want_connection_user: "new_connection_user", - want_connection_password: "new_connection_password", - want_fetcher_query: "new_fetcher_query", - want_initialize_caches_timeout_ms: 2000, - want_initialize_caches_query: "new_initialize_caches_query", - want_poll_for_updates_refresh_rate_seconds: 2000, - want_poll_for_updates_timeout_ms: 2000, - want_poll_for_updates_query: "new_poll_for_updates_query", - }, - { - description: "New config and old config set", - config: storedResponsesConfigs.both, - - want_connection_dbname: "new_connection_dbname", - want_connection_host: "new_connection_host", - want_connection_port: 2000, - want_connection_user: "new_connection_user", - want_connection_password: "new_connection_password", - want_fetcher_query: "new_fetcher_query", - want_initialize_caches_timeout_ms: 2000, - want_initialize_caches_query: "new_initialize_caches_query", - want_poll_for_updates_refresh_rate_seconds: 2000, - want_poll_for_updates_timeout_ms: 2000, - want_poll_for_updates_query: "new_poll_for_updates_query", - }, - } - - for _, tt := range storedResponsesTests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigDatabaseConnection(v) - - if len(tt.config) > 0 { - assert.Equal(t, tt.want_connection_dbname, v.Get("stored_responses.database.connection.dbname").(string), tt.description) - assert.Equal(t, tt.want_connection_host, v.Get("stored_responses.database.connection.host").(string), tt.description) - assert.Equal(t, tt.want_connection_port, v.Get("stored_responses.database.connection.port").(int), tt.description) - assert.Equal(t, tt.want_connection_user, v.Get("stored_responses.database.connection.user").(string), tt.description) - assert.Equal(t, tt.want_connection_password, v.Get("stored_responses.database.connection.password").(string), tt.description) - assert.Equal(t, tt.want_fetcher_query, v.Get("stored_responses.database.fetcher.query").(string), tt.description) - assert.Equal(t, tt.want_initialize_caches_timeout_ms, v.Get("stored_responses.database.initialize_caches.timeout_ms").(int), tt.description) - assert.Equal(t, tt.want_initialize_caches_query, v.Get("stored_responses.database.initialize_caches.query").(string), tt.description) - assert.Equal(t, tt.want_poll_for_updates_refresh_rate_seconds, v.Get("stored_responses.database.poll_for_updates.refresh_rate_seconds").(int), tt.description) - assert.Equal(t, tt.want_poll_for_updates_timeout_ms, v.Get("stored_responses.database.poll_for_updates.timeout_ms").(int), tt.description) - assert.Equal(t, tt.want_poll_for_updates_query, v.Get("stored_responses.database.poll_for_updates.query").(string), tt.description) - } else { - assert.Nil(t, v.Get("stored_responses.database.connection.dbname"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.connection.host"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.connection.port"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.connection.user"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.connection.password"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.fetcher.query"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.initialize_caches.timeout_ms"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.initialize_caches.query"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.poll_for_updates.refresh_rate_seconds"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.poll_for_updates.timeout_ms"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.poll_for_updates.query"), tt.description) - } - } -} - -func TestMigrateConfigDatabaseConnectionUsingEnvVars(t *testing.T) { - tests := []struct { - description string - prefix string - setDatabaseEnvVars bool - setPostgresEnvVars bool - }{ - { - description: "stored requests old config set", - prefix: "stored_requests", - setPostgresEnvVars: true, - }, - { - description: "stored requests new config set", - prefix: "stored_requests", - setDatabaseEnvVars: true, - }, - { - description: "stored requests old and new config set", - prefix: "stored_requests", - setDatabaseEnvVars: true, - setPostgresEnvVars: true, - }, - { - description: "stored video requests old config set", - prefix: "stored_video_req", - setPostgresEnvVars: true, - }, - { - description: "stored video requests new config set", - prefix: "stored_video_req", - setDatabaseEnvVars: true, - }, - { - description: "stored video requests old and new config set", - prefix: "stored_video_req", - setDatabaseEnvVars: true, - setPostgresEnvVars: true, - }, - { - description: "stored responses old config set", - prefix: "stored_responses", - setPostgresEnvVars: true, - }, - { - description: "stored responses new config set", - prefix: "stored_responses", - setDatabaseEnvVars: true, - }, - { - description: "stored responses old and new config set", - prefix: "stored_responses", - setDatabaseEnvVars: true, - setPostgresEnvVars: true, - }, - } - - pgValues := map[string]string{ - "CONNECTION_DBNAME": "pg-dbname", - "CONNECTION_HOST": "pg-host", - "CONNECTION_PORT": "1", - "CONNECTION_USER": "pg-user", - "CONNECTION_PASSWORD": "pg-password", - "FETCHER_QUERY": "pg-fetcher-query", - "FETCHER_AMP_QUERY": "pg-fetcher-amp-query", - "INITIALIZE_CACHES_TIMEOUT_MS": "2", - "INITIALIZE_CACHES_QUERY": "pg-init-caches-query", - "INITIALIZE_CACHES_AMP_QUERY": "pg-init-caches-amp-query", - "POLL_FOR_UPDATES_REFRESH_RATE_SECONDS": "3", - "POLL_FOR_UPDATES_TIMEOUT_MS": "4", - "POLL_FOR_UPDATES_QUERY": "pg-poll-query $LAST_UPDATED", - "POLL_FOR_UPDATES_AMP_QUERY": "pg-poll-amp-query $LAST_UPDATED", - } - dbValues := map[string]string{ - "CONNECTION_DBNAME": "db-dbname", - "CONNECTION_HOST": "db-host", - "CONNECTION_PORT": "5", - "CONNECTION_USER": "db-user", - "CONNECTION_PASSWORD": "db-password", - "FETCHER_QUERY": "db-fetcher-query", - "FETCHER_AMP_QUERY": "db-fetcher-amp-query", - "INITIALIZE_CACHES_TIMEOUT_MS": "6", - "INITIALIZE_CACHES_QUERY": "db-init-caches-query", - "INITIALIZE_CACHES_AMP_QUERY": "db-init-caches-amp-query", - "POLL_FOR_UPDATES_REFRESH_RATE_SECONDS": "7", - "POLL_FOR_UPDATES_TIMEOUT_MS": "8", - "POLL_FOR_UPDATES_QUERY": "db-poll-query $LAST_UPDATED", - "POLL_FOR_UPDATES_AMP_QUERY": "db-poll-amp-query $LAST_UPDATED", - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - prefix := "PBS_" + strings.ToUpper(tt.prefix) - - // validation rules require in memory cache type to not be "none" - // given that we want to set the poll for update queries to non-empty values - envVarName := prefix + "_IN_MEMORY_CACHE_TYPE" - if oldval, ok := os.LookupEnv(envVarName); ok { - defer os.Setenv(envVarName, oldval) - } else { - defer os.Unsetenv(envVarName) - } - os.Setenv(envVarName, "unbounded") - - if tt.setPostgresEnvVars { - for suffix, v := range pgValues { - envVarName := prefix + "_POSTGRES_" + suffix - if oldval, ok := os.LookupEnv(envVarName); ok { - defer os.Setenv(envVarName, oldval) - } else { - defer os.Unsetenv(envVarName) - } - os.Setenv(envVarName, v) - } - } - if tt.setDatabaseEnvVars { - for suffix, v := range dbValues { - envVarName := prefix + "_DATABASE_" + suffix - if oldval, ok := os.LookupEnv(envVarName); ok { - defer os.Setenv(envVarName, oldval) - } else { - defer os.Unsetenv(envVarName) - } - os.Setenv(envVarName, v) - } - } - - c, _ := newDefaultConfig(t) - - expectedDatabaseValues := map[string]string{} - if tt.setDatabaseEnvVars { - expectedDatabaseValues = dbValues - } else if tt.setPostgresEnvVars { - expectedDatabaseValues = pgValues - } - - if tt.prefix == "stored_requests" { - assert.Equal(t, expectedDatabaseValues["CONNECTION_DBNAME"], c.StoredRequests.Database.ConnectionInfo.Database, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_HOST"], c.StoredRequests.Database.ConnectionInfo.Host, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_PORT"], strconv.Itoa(c.StoredRequests.Database.ConnectionInfo.Port), tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_USER"], c.StoredRequests.Database.ConnectionInfo.Username, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_PASSWORD"], c.StoredRequests.Database.ConnectionInfo.Password, tt.description) - assert.Equal(t, expectedDatabaseValues["FETCHER_QUERY"], c.StoredRequests.Database.FetcherQueries.QueryTemplate, tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_TIMEOUT_MS"], strconv.Itoa(c.StoredRequests.Database.CacheInitialization.Timeout), tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_QUERY"], c.StoredRequests.Database.CacheInitialization.Query, tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_REFRESH_RATE_SECONDS"], strconv.Itoa(c.StoredRequests.Database.PollUpdates.RefreshRate), tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_TIMEOUT_MS"], strconv.Itoa(c.StoredRequests.Database.PollUpdates.Timeout), tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_QUERY"], c.StoredRequests.Database.PollUpdates.Query, tt.description) - // AMP queries are only migrated for stored requests - assert.Equal(t, expectedDatabaseValues["FETCHER_AMP_QUERY"], c.StoredRequests.Database.FetcherQueries.AmpQueryTemplate, tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_AMP_QUERY"], c.StoredRequests.Database.CacheInitialization.AmpQuery, tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_AMP_QUERY"], c.StoredRequests.Database.PollUpdates.AmpQuery, tt.description) - } else if tt.prefix == "stored_video_req" { - assert.Equal(t, expectedDatabaseValues["CONNECTION_DBNAME"], c.StoredVideo.Database.ConnectionInfo.Database, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_HOST"], c.StoredVideo.Database.ConnectionInfo.Host, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_PORT"], strconv.Itoa(c.StoredVideo.Database.ConnectionInfo.Port), tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_USER"], c.StoredVideo.Database.ConnectionInfo.Username, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_PASSWORD"], c.StoredVideo.Database.ConnectionInfo.Password, tt.description) - assert.Equal(t, expectedDatabaseValues["FETCHER_QUERY"], c.StoredVideo.Database.FetcherQueries.QueryTemplate, tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_TIMEOUT_MS"], strconv.Itoa(c.StoredVideo.Database.CacheInitialization.Timeout), tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_QUERY"], c.StoredVideo.Database.CacheInitialization.Query, tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_REFRESH_RATE_SECONDS"], strconv.Itoa(c.StoredVideo.Database.PollUpdates.RefreshRate), tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_TIMEOUT_MS"], strconv.Itoa(c.StoredVideo.Database.PollUpdates.Timeout), tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_QUERY"], c.StoredVideo.Database.PollUpdates.Query, tt.description) - assert.Empty(t, c.StoredVideo.Database.FetcherQueries.AmpQueryTemplate, tt.description) - assert.Empty(t, c.StoredVideo.Database.CacheInitialization.AmpQuery, tt.description) - assert.Empty(t, c.StoredVideo.Database.PollUpdates.AmpQuery, tt.description) - } else { - assert.Equal(t, expectedDatabaseValues["CONNECTION_DBNAME"], c.StoredResponses.Database.ConnectionInfo.Database, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_HOST"], c.StoredResponses.Database.ConnectionInfo.Host, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_PORT"], strconv.Itoa(c.StoredResponses.Database.ConnectionInfo.Port), tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_USER"], c.StoredResponses.Database.ConnectionInfo.Username, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_PASSWORD"], c.StoredResponses.Database.ConnectionInfo.Password, tt.description) - assert.Equal(t, expectedDatabaseValues["FETCHER_QUERY"], c.StoredResponses.Database.FetcherQueries.QueryTemplate, tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_TIMEOUT_MS"], strconv.Itoa(c.StoredResponses.Database.CacheInitialization.Timeout), tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_QUERY"], c.StoredResponses.Database.CacheInitialization.Query, tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_REFRESH_RATE_SECONDS"], strconv.Itoa(c.StoredResponses.Database.PollUpdates.RefreshRate), tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_TIMEOUT_MS"], strconv.Itoa(c.StoredResponses.Database.PollUpdates.Timeout), tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_QUERY"], c.StoredResponses.Database.PollUpdates.Query, tt.description) - assert.Empty(t, c.StoredResponses.Database.FetcherQueries.AmpQueryTemplate, tt.description) - assert.Empty(t, c.StoredResponses.Database.CacheInitialization.AmpQuery, tt.description) - assert.Empty(t, c.StoredResponses.Database.PollUpdates.AmpQuery, tt.description) - } - }) - } -} - -func TestMigrateConfigDatabaseQueryParams(t *testing.T) { - - config := []byte(` - stored_requests: - postgres: - fetcher: - query: - SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) - UNION ALL - SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) - UNION ALL - SELECT * FROM Table3 WHERE id in (%ID_LIST%) - amp_query: - SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) - UNION ALL - SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) - UNION ALL - SELECT * FROM Table3 WHERE id in (%ID_LIST%) - poll_for_updates: - query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" - amp_query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" - stored_video_req: - postgres: - fetcher: - query: - SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) - UNION ALL - SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) - UNION ALL - SELECT * FROM Table3 WHERE id in (%ID_LIST%) - amp_query: - SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) - UNION ALL - SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) - UNION ALL - SELECT * FROM Table3 WHERE id in (%ID_LIST%) - poll_for_updates: - query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" - amp_query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" - stored_responses: - postgres: - fetcher: - query: - SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) - UNION ALL - SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) - UNION ALL - SELECT * FROM Table3 WHERE id in (%ID_LIST%) - amp_query: - SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) - UNION ALL - SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) - UNION ALL - SELECT * FROM Table3 WHERE id in (%ID_LIST%) - poll_for_updates: - query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" - amp_query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" - `) - - want_queries := struct { - fetcher_query string - fetcher_amp_query string - poll_for_updates_query string - poll_for_updates_amp_query string - }{ - fetcher_query: "SELECT * FROM Table1 WHERE id in ($REQUEST_ID_LIST) " + - "UNION ALL " + - "SELECT * FROM Table2 WHERE id in ($IMP_ID_LIST) " + - "UNION ALL " + - "SELECT * FROM Table3 WHERE id in ($ID_LIST)", - fetcher_amp_query: "SELECT * FROM Table1 WHERE id in ($REQUEST_ID_LIST) " + - "UNION ALL " + - "SELECT * FROM Table2 WHERE id in ($IMP_ID_LIST) " + - "UNION ALL " + - "SELECT * FROM Table3 WHERE id in ($ID_LIST)", - poll_for_updates_query: "SELECT * FROM Table1 WHERE last_updated > $LAST_UPDATED UNION ALL SELECT * FROM Table2 WHERE last_updated > $LAST_UPDATED", - poll_for_updates_amp_query: "SELECT * FROM Table1 WHERE last_updated > $LAST_UPDATED UNION ALL SELECT * FROM Table2 WHERE last_updated > $LAST_UPDATED", - } - - v := viper.New() - v.SetConfigType("yaml") - err := v.ReadConfig(bytes.NewBuffer(config)) - assert.NoError(t, err) - - migrateConfigDatabaseConnection(v) - - // stored_requests queries - assert.Equal(t, want_queries.fetcher_query, v.GetString("stored_requests.database.fetcher.query")) - assert.Equal(t, want_queries.fetcher_amp_query, v.GetString("stored_requests.database.fetcher.amp_query")) - assert.Equal(t, want_queries.poll_for_updates_query, v.GetString("stored_requests.database.poll_for_updates.query")) - assert.Equal(t, want_queries.poll_for_updates_amp_query, v.GetString("stored_requests.database.poll_for_updates.amp_query")) - - // stored_video_req queries - assert.Equal(t, want_queries.fetcher_query, v.GetString("stored_video_req.database.fetcher.query")) - assert.Equal(t, want_queries.fetcher_amp_query, v.GetString("stored_video_req.database.fetcher.amp_query")) - assert.Equal(t, want_queries.poll_for_updates_query, v.GetString("stored_video_req.database.poll_for_updates.query")) - assert.Equal(t, want_queries.poll_for_updates_amp_query, v.GetString("stored_video_req.database.poll_for_updates.amp_query")) - - // stored_responses queries - assert.Equal(t, want_queries.fetcher_query, v.GetString("stored_responses.database.fetcher.query")) - assert.Equal(t, want_queries.fetcher_amp_query, v.GetString("stored_responses.database.fetcher.amp_query")) - assert.Equal(t, want_queries.poll_for_updates_query, v.GetString("stored_responses.database.poll_for_updates.query")) - assert.Equal(t, want_queries.poll_for_updates_amp_query, v.GetString("stored_responses.database.poll_for_updates.amp_query")) -} - func TestIsConfigInfoPresent(t *testing.T) { configPrefix1Field2Only := []byte(` prefix1: From 0d488eea2c4f1f9d8835e52d597838b176a453ab Mon Sep 17 00:00:00 2001 From: Mohammad Nurul Islam Shihan <93646635+ishihanvcs@users.noreply.github.com> Date: Fri, 29 Sep 2023 12:36:59 +0600 Subject: [PATCH 024/138] ImproveDigital: updates (#3077) --- adapters/improvedigital/improvedigital.go | 98 +++++--- .../exemplary/app-multi.json | 105 +++++++++ .../improvedigitaltest/exemplary/audio.json | 169 ++++++++++++++ .../improvedigitaltest/exemplary/native.json | 12 +- .../exemplary/site-multi.json | 111 +++++++++ .../missing_and_unsupported_mtype.json | 215 ++++++++++++++++++ static/bidder-info/improvedigital.yaml | 4 +- 7 files changed, 685 insertions(+), 29 deletions(-) create mode 100644 adapters/improvedigital/improvedigitaltest/exemplary/audio.json create mode 100644 adapters/improvedigital/improvedigitaltest/supplemental/missing_and_unsupported_mtype.json diff --git a/adapters/improvedigital/improvedigital.go b/adapters/improvedigital/improvedigital.go index b934ac753a0..c26a200a486 100644 --- a/adapters/improvedigital/improvedigital.go +++ b/adapters/improvedigital/improvedigital.go @@ -3,19 +3,18 @@ package improvedigital import ( "encoding/json" "fmt" - "net/http" - "strconv" - "strings" - "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "regexp" + "strconv" + "strings" ) const ( - buyingTypeRTB = "rtb" isRewardedInventory = "is_rewarded_inventory" stateRewardedInventoryEnable = "1" consentProvidersSettingsInputKey = "ConsentedProvidersSettings" @@ -43,6 +42,8 @@ type ImpExtBidder struct { } } +var dealDetectionRegEx = regexp.MustCompile("(classic|deal)") + // MakeRequests makes the HTTP requests which should be made to fetch bids. func (a *ImprovedigitalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { numRequests := len(request.Imp) @@ -119,6 +120,7 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e } var bidResp openrtb2.BidResponse + var impMap = make(map[string]openrtb2.Imp) if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -134,7 +136,6 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e } seatBid := bidResp.SeatBid[0] - if len(seatBid.Bid) == 0 { return nil, nil } @@ -142,10 +143,14 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(seatBid.Bid)) bidResponse.Currency = bidResp.Cur + for i := range internalRequest.Imp { + impMap[internalRequest.Imp[i].ID] = internalRequest.Imp[i] + } + for i := range seatBid.Bid { bid := seatBid.Bid[i] - bidType, err := getMediaTypeForImp(bid.ImpID, internalRequest.Imp) + bidType, err := getBidType(bid, impMap) if err != nil { return nil, []error{err} } @@ -158,7 +163,7 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e } bidExtImprovedigital := bidExt.Improvedigital - if bidExtImprovedigital.LineItemID != 0 && bidExtImprovedigital.BuyingType != "" && bidExtImprovedigital.BuyingType != buyingTypeRTB { + if bidExtImprovedigital.LineItemID != 0 && dealDetectionRegEx.MatchString(bidExtImprovedigital.BuyingType) { bid.DealID = strconv.Itoa(bidExtImprovedigital.LineItemID) } } @@ -169,7 +174,6 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e }) } return bidResponse, nil - } // Builder builds a new instance of the Improvedigital adapter for the given bidder with the given config. @@ -180,31 +184,73 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return bidder, nil } -func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { - for _, imp := range imps { - if imp.ID == impID { - if imp.Banner != nil { - return openrtb_ext.BidTypeBanner, nil - } - - if imp.Video != nil { - return openrtb_ext.BidTypeVideo, nil - } +func getBidType(bid openrtb2.Bid, impMap map[string]openrtb2.Imp) (openrtb_ext.BidType, error) { + // there must be a matching imp against bid.ImpID + imp, found := impMap[bid.ImpID] + if !found { + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", bid.ImpID), + } + } - if imp.Native != nil { - return openrtb_ext.BidTypeNative, nil + // if MType is not set in server response, try to determine it + if bid.MType == 0 { + if !isMultiFormatImp(imp) { + // Not a bid for multi format impression. So, determine MType from impression + if imp.Banner != nil { + bid.MType = openrtb2.MarkupBanner + } else if imp.Video != nil { + bid.MType = openrtb2.MarkupVideo + } else if imp.Audio != nil { + bid.MType = openrtb2.MarkupAudio + } else if imp.Native != nil { + bid.MType = openrtb2.MarkupNative + } else { // This should not happen. + // Let's handle it just in case by returning an error. + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Could not determine MType from impression with ID: \"%s\"", bid.ImpID), + } } - + } else { return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), + Message: fmt.Sprintf("Bid must have non-zero MType for multi format impression with ID: \"%s\"", bid.ImpID), } } } - // This shouldnt happen. Lets handle it just incase by returning an error. - return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), + // map MType to BidType + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + // This shouldn't happen. Let's handle it just in case by returning an error. + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unsupported MType %d for impression with ID: \"%s\"", bid.MType, bid.ImpID), + } + } +} + +func isMultiFormatImp(imp openrtb2.Imp) bool { + formatCount := 0 + if imp.Banner != nil { + formatCount++ + } + if imp.Video != nil { + formatCount++ + } + if imp.Audio != nil { + formatCount++ + } + if imp.Native != nil { + formatCount++ } + return formatCount > 1 } // This method responsible to clone request and convert additional consent providers string to array when additional consent provider found diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json b/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json index 6963e82f9c4..425435049db 100644 --- a/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json +++ b/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json @@ -40,6 +40,28 @@ "placementId": 13244 } } + }, + { + "id": "test-multi-format-id-with-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } } ] }, @@ -159,6 +181,71 @@ "cur": "USD" } } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "app": { + "id": "appID", + "publisher": { + "id": "uniq_pub_id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "imp": [ + { + "id": "test-multi-format-id-with-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid2", + "impid": "test-multi-format-id-with-mtype", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } } ], "expectedBidResponses": [ @@ -197,6 +284,24 @@ "type": "video" } ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid2", + "impid": "test-multi-format-id-with-mtype", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080, + "mtype": 1 + }, + "type": "banner" + } + ] } ] } diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/audio.json b/adapters/improvedigital/improvedigitaltest/exemplary/audio.json new file mode 100644 index 00000000000..9ddbf799517 --- /dev/null +++ b/adapters/improvedigital/improvedigitaltest/exemplary/audio.json @@ -0,0 +1,169 @@ +{ + "mockBidRequest": { + "id": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "source": { + "tid": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "ext": { + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { "asi": "example.com", "hp": 1, "sid": "1234abc" } + ] + } + } + }, + "tmax": 1000, + "imp": [ + { + "id": "audio", + "audio": { + "mimes": ["audio/mp3"], + "protocols": [2, 5] + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + }, + "site": { + "publisher": { "id": "pubid" }, + "page": "example.com" + }, + "device": { "w": 1581, "h": 922, "ip": "1.1.1.1" }, + "regs": { "ext": { "gdpr": 0 } }, + "user": { + "ext": { + "consent": "XYZ" + } + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "imp": [ + { + "id": "audio", + "audio": { + "mimes": ["audio/mp3"], + "protocols": [2, 5] + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ], + "site": { + "page": "example.com", + "publisher": { "id": "pubid" } + }, + "device": { + "ip": "1.1.1.1", + "h": 922, + "w": 1581 + }, + "user": { + "ext": { + "consent": "XYZ" + } + }, + "tmax": 1000, + "source": { + "tid": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "ext": { + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "example.com", + "hp": 1, + "sid": "1234abc" + } + ] + } + } + }, + "regs": { "ext": { "gdpr": 0 } }, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "ext": { + "improvedigital": { + "brand_name": "AdvertiserABC", + "bidder_id": 301 + } + }, + "crid": "14065", + "id": "d4f04449-ba04-4d7c-bb34-dc0fc5240f59", + "price": 0.01, + "adm": "some-test-ad-vast", + "adomain": ["example.com"], + "impid": "audio", + "cid": "25076" + } + ], + "seat": "improvedigital" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "d4f04449-ba04-4d7c-bb34-dc0fc5240f59", + "impid": "audio", + "price": 0.01, + "adm": "some-test-ad-vast", + "adomain": ["example.com"], + "cid": "25076", + "crid": "14065", + "ext": { + "improvedigital": { + "brand_name": "AdvertiserABC", + "bidder_id": 301 + } + } + }, + "type": "audio" + } + ] + } + ] +} diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/native.json b/adapters/improvedigital/improvedigitaltest/exemplary/native.json index 3309b35a753..3865d40383b 100644 --- a/adapters/improvedigital/improvedigitaltest/exemplary/native.json +++ b/adapters/improvedigital/improvedigitaltest/exemplary/native.json @@ -17,7 +17,11 @@ "imp": [ { "id": "native", - "ext": { "improvedigital": { "placementId": 1234 } }, + "ext": { + "bidder": { + "placementId": 1234 + } + }, "native": { "request": "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1]}],\"assets\":[{\"required\":1,\"title\":{\"len\":80}},{\"required\":1,\"data\":{\"type\":2}},{\"required\":0,\"data\":{\"type\":12}},{\"required\":0,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":225,\"ext\":{\"aspectratios\":[\"4:3\"]}}},{\"required\":0,\"img\":{\"type\":1,\"w\":128,\"h\":128}},{\"required\":0,\"data\":{\"type\":3}},{\"required\":0,\"data\":{\"type\":6}}]}", "ver": "1.2" @@ -57,7 +61,11 @@ "request": "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1]}],\"assets\":[{\"required\":1,\"title\":{\"len\":80}},{\"required\":1,\"data\":{\"type\":2}},{\"required\":0,\"data\":{\"type\":12}},{\"required\":0,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":225,\"ext\":{\"aspectratios\":[\"4:3\"]}}},{\"required\":0,\"img\":{\"type\":1,\"w\":128,\"h\":128}},{\"required\":0,\"data\":{\"type\":3}},{\"required\":0,\"data\":{\"type\":6}}]}", "ver": "1.2" }, - "ext": { "improvedigital": { "placementId": 1234 } } + "ext": { + "bidder": { + "placementId": 1234 + } + } } ], "site": { diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json b/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json index 0945b4c0f69..d7131a54577 100644 --- a/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json +++ b/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json @@ -42,6 +42,28 @@ "placementId": 13244 } } + }, + { + "id": "test-multi-format-id-with-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } } ] }, @@ -166,6 +188,75 @@ "cur": "USD" } } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url", + "domain": "good.site", + "publisher": { + "id": "uniq_pub_id" + }, + "keywords": "omgword", + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-multi-format-id-with-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid1", + "impid": "test-multi-format-id-with-mtype", + "price": 0.5, + "adid": "12345678", + "adm": "some-test-ad-html", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } } ], @@ -205,6 +296,26 @@ "type": "video" } ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid1", + "impid": "test-multi-format-id-with-mtype", + "price": 0.5, + "adm": "some-test-ad-html", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] } ] } diff --git a/adapters/improvedigital/improvedigitaltest/supplemental/missing_and_unsupported_mtype.json b/adapters/improvedigital/improvedigitaltest/supplemental/missing_and_unsupported_mtype.json new file mode 100644 index 00000000000..ad82321a401 --- /dev/null +++ b/adapters/improvedigital/improvedigitaltest/supplemental/missing_and_unsupported_mtype.json @@ -0,0 +1,215 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + }, + { + "id": "test-multi-format-id-without-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + }, + { + "id": "test-unsupported-format-id-without-mtype", + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "improvedigital", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 5 + }] + }], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-multi-format-id-without-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid2", + "impid": "test-multi-format-id-without-mtype", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080 + } + ] + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-unsupported-format-id-without-mtype", + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid3", + "impid": "test-unsupported-format-id-without-mtype", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unsupported MType 5 for impression with ID: \"test-imp-id\"", + "comparison": "literal" + }, + { + "value": "Bid must have non-zero MType for multi format impression with ID: \"test-multi-format-id-without-mtype\"", + "comparison": "literal" + }, + { + "value": "Could not determine MType from impression with ID: \"test-unsupported-format-id-without-mtype\"", + "comparison": "literal" + } + ] +} diff --git a/static/bidder-info/improvedigital.yaml b/static/bidder-info/improvedigital.yaml index d14356cde0e..4cd30d7c6b2 100644 --- a/static/bidder-info/improvedigital.yaml +++ b/static/bidder-info/improvedigital.yaml @@ -7,14 +7,16 @@ capabilities: mediaTypes: - banner - video + - audio - native site: mediaTypes: - banner - video + - audio - native userSync: redirect: - url: "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + url: "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" userMacro: "{PUB_USER_ID}" endpointCompression: "GZIP" \ No newline at end of file From 6a361780558326ab962383aa954a0f684261d689 Mon Sep 17 00:00:00 2001 From: Arne Schulz Date: Fri, 29 Sep 2023 10:09:43 +0200 Subject: [PATCH 025/138] [ORBIDDER] add user sync redirect url (#3118) --- static/bidder-info/orbidder.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/bidder-info/orbidder.yaml b/static/bidder-info/orbidder.yaml index 705d35a5a20..74348c75d7f 100644 --- a/static/bidder-info/orbidder.yaml +++ b/static/bidder-info/orbidder.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - native +userSync: + redirect: + url: "https://orbidder.otto.de/pbs-usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&redirect={{.RedirectURL}}" + userMacro: "[ODN_ID]" \ No newline at end of file From 4ad946bb56e59b71dc01c7caf3954f1bd4d6f0e7 Mon Sep 17 00:00:00 2001 From: Mikael Lundin Date: Fri, 29 Sep 2023 10:14:35 +0200 Subject: [PATCH 026/138] Adnuntius: Add Price Type (#3084) --- adapters/adnuntius/adnuntius.go | 46 ++++++-- .../supplemental/check-gross-bids.json | 103 ++++++++++++++++++ .../supplemental/check-net-bids.json | 103 ++++++++++++++++++ .../supplemental/check-price-type-error.json | 38 +++++++ openrtb_ext/imp_adnuntius.go | 1 + static/bidder-params/adnuntius.json | 8 ++ 6 files changed, 292 insertions(+), 7 deletions(-) create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-price-type-error.json diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go index 3f91d7403f0..fd667ddc506 100644 --- a/adapters/adnuntius/adnuntius.go +++ b/adapters/adnuntius/adnuntius.go @@ -39,6 +39,12 @@ type Ad struct { Amount float64 Currency string } + NetBid struct { + Amount float64 + } + GrossBid struct { + Amount float64 + } DealID string `json:"dealId,omitempty"` AdId string CreativeWidth string @@ -194,6 +200,7 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters Message: fmt.Sprintf("ignoring imp id=%s, Adnuntius supports only Banner", imp.ID), }} } + var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, []error{&errortypes.BadInput{ @@ -204,7 +211,7 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters var adnuntiusExt openrtb_ext.ImpExtAdnunitus if err := json.Unmarshal(bidderExt.Bidder, &adnuntiusExt); err != nil { return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Error unmarshalling ExtImpBmtm: %s", err.Error()), + Message: fmt.Sprintf("Error unmarshalling ExtImpValues: %s", err.Error()), }} } @@ -328,7 +335,7 @@ func getGDPR(request *openrtb2.BidRequest) (string, string, error) { return gdpr, consent, nil } -func generateAdResponse(ad Ad, impId string, html string, request *openrtb2.BidRequest) (*openrtb2.Bid, []error) { +func generateAdResponse(ad Ad, imp openrtb2.Imp, html string, request *openrtb2.BidRequest) (*openrtb2.Bid, []error) { creativeWidth, widthErr := strconv.ParseInt(ad.CreativeWidth, 10, 64) if widthErr != nil { @@ -344,6 +351,31 @@ func generateAdResponse(ad Ad, impId string, html string, request *openrtb2.BidR }} } + price := ad.Bid.Amount + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error unmarshalling ExtImpBidder: %s", err.Error()), + }} + } + + var adnuntiusExt openrtb_ext.ImpExtAdnunitus + if err := json.Unmarshal(bidderExt.Bidder, &adnuntiusExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error unmarshalling ExtImpValues: %s", err.Error()), + }} + } + + if adnuntiusExt.BidType != "" { + if strings.EqualFold(string(adnuntiusExt.BidType), "net") { + price = ad.NetBid.Amount + } + if strings.EqualFold(string(adnuntiusExt.BidType), "gross") { + price = ad.GrossBid.Amount + } + } + adDomain := []string{} for _, url := range ad.DestinationUrls { domainArray := strings.Split(url, "/") @@ -353,14 +385,14 @@ func generateAdResponse(ad Ad, impId string, html string, request *openrtb2.BidR bid := openrtb2.Bid{ ID: ad.AdId, - ImpID: impId, + ImpID: imp.ID, W: creativeWidth, H: creativeHeight, AdID: ad.AdId, DealID: ad.DealID, CID: ad.LineItemId, CrID: ad.CreativeId, - Price: ad.Bid.Amount * 1000, + Price: price * 1000, AdM: html, ADomain: adDomain, } @@ -377,7 +409,7 @@ func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest) adunitMap[adnRespAdunit.TargetId] = adnRespAdunit } - for i, imp := range request.Imp { + for _, imp := range request.Imp { auId, _, _, err := jsonparser.Get(imp.Ext, "bidder", "auId") if err != nil { @@ -394,7 +426,7 @@ func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest) ad := adunit.Ads[0] currency = ad.Bid.Currency - adBid, err := generateAdResponse(ad, request.Imp[i].ID, adunit.Html, request) + adBid, err := generateAdResponse(ad, imp, adunit.Html, request) if err != nil { return nil, []error{&errortypes.BadInput{ Message: fmt.Sprintf("Error at ad generation"), @@ -407,7 +439,7 @@ func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest) }) for _, deal := range adunit.Deals { - dealBid, err := generateAdResponse(deal, request.Imp[i].ID, deal.Html, request) + dealBid, err := generateAdResponse(deal, imp, deal.Html, request) if err != nil { return nil, []error{&errortypes.BadInput{ Message: fmt.Sprintf("Error at ad generation"), diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json b/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json new file mode 100644 index 00000000000..d6301fe71cf --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json @@ -0,0 +1,103 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123", + "bidType": "gross" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "metaData": { + "usi": "1kjh3429kjh295jkl" + }, + "context": "unknown" + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { "amount": 20.0, "currency": "NOK" }, + "grossBid": {"amount": 0.1, "currency": "NOK"}, + "netBid": {"amount": 0.075, "currency": "NOK"}, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 100, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": [ + "google.com" + ], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json b/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json new file mode 100644 index 00000000000..ebb25b2b7ad --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json @@ -0,0 +1,103 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123", + "bidType": "net" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "metaData": { + "usi": "1kjh3429kjh295jkl" + }, + "context": "unknown" + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { "amount": 20.0, "currency": "NOK" }, + "grossBid": {"amount": 0.1, "currency": "NOK"}, + "netBid": {"amount": 0.075, "currency": "NOK"}, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 75, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": [ + "google.com" + ], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-price-type-error.json b/adapters/adnuntius/adnuntiustest/supplemental/check-price-type-error.json new file mode 100644 index 00000000000..89016087a43 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-price-type-error.json @@ -0,0 +1,38 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123", + "bidType": 123 + } + } + } + ] + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "Error unmarshalling ExtImpValues: json: cannot unmarshal number into Go struct field ImpExtAdnunitus.bidType of type string", + "comparison": "literal" + } + ] +} diff --git a/openrtb_ext/imp_adnuntius.go b/openrtb_ext/imp_adnuntius.go index 86023c48231..49833e90f1d 100644 --- a/openrtb_ext/imp_adnuntius.go +++ b/openrtb_ext/imp_adnuntius.go @@ -5,4 +5,5 @@ type ImpExtAdnunitus struct { Network string `json:"network"` NoCookies bool `json:"noCookies"` MaxDeals int `json:"maxDeals"` + BidType string `json:"bidType,omitempty"` } diff --git a/static/bidder-params/adnuntius.json b/static/bidder-params/adnuntius.json index ff975501edb..f7ab32d2f48 100644 --- a/static/bidder-params/adnuntius.json +++ b/static/bidder-params/adnuntius.json @@ -9,6 +9,10 @@ "type": "string", "description": "Placement ID" }, + "maxDeals": { + "type": "integer", + "description": "Specify how many deals that you want to return from the auction." + }, "network": { "type": "string", "description": "Network if required" @@ -16,6 +20,10 @@ "noCookies": { "type": "boolean", "description": "Disable cookies being set by the ad server." + }, + "bidType": { + "type": "string", + "description": "Allows you to specify Net or Gross bids." } }, From 9e14c0905241bff942d566321a251b1986a74ea6 Mon Sep 17 00:00:00 2001 From: SerhiiNahornyi Date: Fri, 29 Sep 2023 11:32:12 +0300 Subject: [PATCH 027/138] Rubicon: Remove eids logic (#3111) --- adapters/rubicon/rubicon.go | 82 +--- adapters/rubicon/rubicon_test.go | 91 ----- ...-special-uid-with-other-stype-present.json | 372 ----------------- ...-special-uid-with-ppuid-stype-present.json | 372 ----------------- .../exemplary/user-buyeruid-present.json | 373 ------------------ 5 files changed, 4 insertions(+), 1286 deletions(-) delete mode 100644 adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-other-stype-present.json delete mode 100644 adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-ppuid-stype-present.json delete mode 100644 adapters/rubicon/rubicontest/exemplary/user-buyeruid-present.json diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index a7ab9ad8f59..635f6e14b42 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -98,11 +98,10 @@ type rubiconDataExt struct { } type rubiconUserExt struct { - Eids []openrtb2.EID `json:"eids,omitempty"` - RP rubiconUserExtRP `json:"rp"` - LiverampIdl string `json:"liveramp_idl,omitempty"` - Data json.RawMessage `json:"data,omitempty"` - Consent string `json:"consent,omitempty"` + Eids []openrtb2.EID `json:"eids,omitempty"` + RP rubiconUserExtRP `json:"rp"` + Data json.RawMessage `json:"data,omitempty"` + Consent string `json:"consent,omitempty"` } type rubiconSiteExtRP struct { @@ -340,19 +339,6 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada if userBuyerUID == "" { userBuyerUID = extractUserBuyerUID(userExtRP.Eids) } - - mappedRubiconUidsParam, errors := getSegments(userExtRP.Eids) - if len(errors) > 0 { - errs = append(errs, errors...) - continue - } - - if err := updateUserExtWithSegments(&userExtRP, mappedRubiconUidsParam); err != nil { - errs = append(errs, err) - continue - } - - userExtRP.LiverampIdl = mappedRubiconUidsParam.liverampIdl } if userCopy.Consent != "" { @@ -941,66 +927,6 @@ func extractUserBuyerUID(eids []openrtb2.EID) string { return "" } -func getSegments(eids []openrtb2.EID) (mappedRubiconUidsParam, []error) { - rubiconUidsParam := mappedRubiconUidsParam{ - segments: make([]string, 0), - } - errs := make([]error, 0) - - for _, eid := range eids { - switch eid.Source { - case "liveintent.com": - uids := eid.UIDs - if len(uids) > 0 { - if eid.Ext != nil { - var eidExt rubiconUserExtEidExt - if err := json.Unmarshal(eid.Ext, &eidExt); err != nil { - errs = append(errs, &errortypes.BadInput{ - Message: err.Error(), - }) - continue - } - rubiconUidsParam.segments = eidExt.Segments - } - } - case "liveramp.com": - uids := eid.UIDs - if len(uids) > 0 { - uidId := uids[0].ID - if uidId != "" && rubiconUidsParam.liverampIdl == "" { - rubiconUidsParam.liverampIdl = uidId - } - } - } - } - - return rubiconUidsParam, errs -} - -func updateUserExtWithSegments(userExtRP *rubiconUserExt, rubiconUidsParam mappedRubiconUidsParam) error { - if len(rubiconUidsParam.segments) > 0 { - - if rubiconUidsParam.segments != nil { - userExtRPTarget := make(map[string]interface{}) - - if userExtRP.RP.Target != nil { - if err := json.Unmarshal(userExtRP.RP.Target, &userExtRPTarget); err != nil { - return &errortypes.BadInput{Message: err.Error()} - } - } - - userExtRPTarget["LIseg"] = rubiconUidsParam.segments - - if target, err := json.Marshal(&userExtRPTarget); err != nil { - return &errortypes.BadInput{Message: err.Error()} - } else { - userExtRP.RP.Target = target - } - } - } - return nil -} - func isVideo(imp openrtb2.Imp) bool { video := imp.Video if video != nil { diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 6347415547b..893cceb8cf7 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -644,97 +644,6 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) { assert.Equal(t, badvOverflowed[:50], badvRequest, "Unexpected dfp_ad_unit_code: %s") } -func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { - bidder := new(RubiconAdapter) - - request := &openrtb2.BidRequest{ - ID: "test-request-id", - Imp: []openrtb2.Imp{{ - ID: "test-imp-id", - Banner: &openrtb2.Banner{ - Format: []openrtb2.Format{ - {W: 300, H: 250}, - }, - }, - Ext: json.RawMessage(`{"bidder": { - "zoneId": 8394, - "siteId": 283282, - "accountId": 7891 - }}`), - }}, - App: &openrtb2.App{ - ID: "com.test", - Name: "testApp", - }, - User: &openrtb2.User{ - Ext: json.RawMessage(`{"eids": [ - { - "source": "pubcid", - "uids": [{ - "id": "2402fc76-7b39-4f0e-bfc2-060ef7693648" - }] - }, - { - "source": "adserver.org", - "uids": [{ - "id": "3d50a262-bd8e-4be3-90b8-246291523907", - "ext": { - "rtiPartner": "TDID" - } - }] - }, - { - "source": "liveintent.com", - "uids": [{ - "id": "T7JiRRvsRAmh88" - }], - "ext": { - "segments": ["999","888"] - } - }, - { - "source": "liveramp.com", - "uids": [{ - "id": "LIVERAMPID" - }], - "ext": { - "segments": ["111","222"] - } - } - ]}`), - }, - } - - reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - - rubiconReq := &openrtb2.BidRequest{} - if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { - t.Fatalf("Unexpected error while decoding request: %s", err) - } - - assert.NotNil(t, rubiconReq.User.Ext, "User.Ext object should not be nil.") - - var userExt rubiconUserExt - if err := json.Unmarshal(rubiconReq.User.Ext, &userExt); err != nil { - t.Fatal("Error unmarshalling request.user.ext object.") - } - - assert.NotNil(t, userExt.Eids) - assert.Equal(t, 4, len(userExt.Eids), "Eids values are not as expected!") - - // liveramp.com - assert.Equal(t, "LIVERAMPID", userExt.LiverampIdl, "Liveramp_idl value is not as expected!") - - userExtRPTarget := make(map[string]interface{}) - if err := json.Unmarshal(userExt.RP.Target, &userExtRPTarget); err != nil { - t.Fatal("Error unmarshalling request.user.ext.rp.target object.") - } - - assert.Contains(t, userExtRPTarget, "LIseg", "request.user.ext.rp.target value is not as expected!") - assert.Contains(t, userExtRPTarget["LIseg"], "888", "No segment with 888 as expected!") - assert.Contains(t, userExtRPTarget["LIseg"], "999", "No segment with 999 as expected!") -} - func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t *testing.T) { bidder := new(RubiconAdapter) diff --git a/adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-other-stype-present.json b/adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-other-stype-present.json deleted file mode 100644 index dea75ac513e..00000000000 --- a/adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-other-stype-present.json +++ /dev/null @@ -1,372 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch" - }, - "user": { - "yob": 2000, - "geo": { - "country": "USA", - "lat": 47.627500, - "lon": -122.346200 - }, - "gender": "f", - "data": [ - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy" - } - ] - }, - { - "ext": { - "segtax": "someValue" - }, - "segment": [ - { - "id": "shouldNotBeCopied" - } - ] - }, - { - "ext": { - "segtax": "4" - }, - "segment": [ - { - "id": "shouldNotBeCopied2" - } - ] - }, - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy2" - } - ] - }, - { - "ext": { - "segtax": [ - 4 - ] - }, - "segment": [ - { - "id": "shouldNotBeCopied3" - } - ] - } - ], - "keywords": "someKeywords", - "ext": { - "rp": { - "target": { - "someKey": "someValue" - } - }, - "data": { - "dataKey1": "dataValue1", - "dataKey2": [ - "dataValue2", - "dataValue3" - ], - "dataKey3": true - }, - "eids": [ - { - "source": "rubiconproject.com", - "uids": [ - { - "id": "uidId", - "ext": { - "stype": "other" - } - } - ] - } - ] - } - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "video": { - "placement": 3, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "data": { - "adserver": { - "name": "gam", - "adslot": "someAdSlot" - }, - "dataAttr1": "dataVal1", - "dataAttr2": "dataVal2" - }, - "bidder": { - "video": { - }, - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "uri?tk_xint=pbs-test-tracker", - "body": { - "id": "test-request-id", - "device": { - "ext": { - "rp": { - "pixelratio": 0 - } - }, - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - } - }, - "user": { - "buyeruid": "uidId", - "data": [ - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy" - } - ] - }, - { - "ext": { - "segtax": "someValue" - }, - "segment": [ - { - "id": "shouldNotBeCopied" - } - ] - }, - { - "ext": { - "segtax": "4" - }, - "segment": [ - { - "id": "shouldNotBeCopied2" - } - ] - }, - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy2" - } - ] - }, - { - "ext": { - "segtax": [ - 4 - ] - }, - "segment": [ - { - "id": "shouldNotBeCopied3" - } - ] - } - ], - "ext": { - "rp": { - "target": { - "someKey": "someValue", - "dataKey1": [ - "dataValue1" - ], - "dataKey2": [ - "dataValue2", - "dataValue3" - ], - "dataKey3": [ - "true" - ], - "iab": [ - "idToCopy", - "idToCopy2" - ] - } - }, - "eids": [ - { - "source": "rubiconproject.com", - "uids": [ - { - "id": "uidId", - "ext": { - "stype": "other" - } - } - ] - } - ] - }, - "keywords": "someKeywords" - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "secure": 1, - "video": { - "placement": 3, - "ext": { - "rp": { - "size_id": 203 - } - }, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "rp": { - "target": { - "dataAttr1": [ - "dataVal1" - ], - "dataAttr2": [ - "dataVal2" - ], - "page": [ - "somePage" - ], - "ref": [ - "someRef" - ], - "search": [ - "someSearch" - ], - "dfp_ad_unit_code": "someAdSlot" - }, - "track": { - "mint": "", - "mint_version": "" - }, - "zone_id": 535510 - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - } - ], - "seat": "adman" - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-ppuid-stype-present.json b/adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-ppuid-stype-present.json deleted file mode 100644 index c2b129ebdba..00000000000 --- a/adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-ppuid-stype-present.json +++ /dev/null @@ -1,372 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch" - }, - "user": { - "yob": 2000, - "geo": { - "country": "USA", - "lat": 47.627500, - "lon": -122.346200 - }, - "gender": "f", - "data": [ - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy" - } - ] - }, - { - "ext": { - "segtax": "someValue" - }, - "segment": [ - { - "id": "shouldNotBeCopied" - } - ] - }, - { - "ext": { - "segtax": "4" - }, - "segment": [ - { - "id": "shouldNotBeCopied2" - } - ] - }, - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy2" - } - ] - }, - { - "ext": { - "segtax": [ - 4 - ] - }, - "segment": [ - { - "id": "shouldNotBeCopied3" - } - ] - } - ], - "keywords": "someKeywords", - "ext": { - "rp": { - "target": { - "someKey": "someValue" - } - }, - "data": { - "dataKey1": "dataValue1", - "dataKey2": [ - "dataValue2", - "dataValue3" - ], - "dataKey3": true - }, - "eids": [ - { - "source": "rubiconproject.com", - "uids": [ - { - "id": "uidId", - "ext": { - "stype": "ppuid" - } - } - ] - } - ] - } - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "video": { - "placement": 3, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "data": { - "adserver": { - "name": "gam", - "adslot": "someAdSlot" - }, - "dataAttr1": "dataVal1", - "dataAttr2": "dataVal2" - }, - "bidder": { - "video": { - }, - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "uri?tk_xint=pbs-test-tracker", - "body": { - "id": "test-request-id", - "device": { - "ext": { - "rp": { - "pixelratio": 0 - } - }, - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - } - }, - "user": { - "buyeruid": "uidId", - "data": [ - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy" - } - ] - }, - { - "ext": { - "segtax": "someValue" - }, - "segment": [ - { - "id": "shouldNotBeCopied" - } - ] - }, - { - "ext": { - "segtax": "4" - }, - "segment": [ - { - "id": "shouldNotBeCopied2" - } - ] - }, - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy2" - } - ] - }, - { - "ext": { - "segtax": [ - 4 - ] - }, - "segment": [ - { - "id": "shouldNotBeCopied3" - } - ] - } - ], - "ext": { - "rp": { - "target": { - "someKey": "someValue", - "dataKey1": [ - "dataValue1" - ], - "dataKey2": [ - "dataValue2", - "dataValue3" - ], - "dataKey3": [ - "true" - ], - "iab": [ - "idToCopy", - "idToCopy2" - ] - } - }, - "eids": [ - { - "source": "rubiconproject.com", - "uids": [ - { - "id": "uidId", - "ext": { - "stype": "ppuid" - } - } - ] - } - ] - }, - "keywords": "someKeywords" - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "secure": 1, - "video": { - "placement": 3, - "ext": { - "rp": { - "size_id": 203 - } - }, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "rp": { - "target": { - "dataAttr1": [ - "dataVal1" - ], - "dataAttr2": [ - "dataVal2" - ], - "page": [ - "somePage" - ], - "ref": [ - "someRef" - ], - "search": [ - "someSearch" - ], - "dfp_ad_unit_code": "someAdSlot" - }, - "track": { - "mint": "", - "mint_version": "" - }, - "zone_id": 535510 - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - } - ], - "seat": "adman" - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/rubicon/rubicontest/exemplary/user-buyeruid-present.json b/adapters/rubicon/rubicontest/exemplary/user-buyeruid-present.json deleted file mode 100644 index 458e19ee5a3..00000000000 --- a/adapters/rubicon/rubicontest/exemplary/user-buyeruid-present.json +++ /dev/null @@ -1,373 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch" - }, - "user": { - "buyeruid": "buyeruid", - "yob": 2000, - "geo": { - "country": "USA", - "lat": 47.627500, - "lon": -122.346200 - }, - "gender": "f", - "data": [ - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy" - } - ] - }, - { - "ext": { - "segtax": "someValue" - }, - "segment": [ - { - "id": "shouldNotBeCopied" - } - ] - }, - { - "ext": { - "segtax": "4" - }, - "segment": [ - { - "id": "shouldNotBeCopied2" - } - ] - }, - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy2" - } - ] - }, - { - "ext": { - "segtax": [ - 4 - ] - }, - "segment": [ - { - "id": "shouldNotBeCopied3" - } - ] - } - ], - "keywords": "someKeywords", - "ext": { - "rp": { - "target": { - "someKey": "someValue" - } - }, - "data": { - "dataKey1": "dataValue1", - "dataKey2": [ - "dataValue2", - "dataValue3" - ], - "dataKey3": true - }, - "eids": [ - { - "source": "rubiconproject.com", - "uids": [ - { - "id": "uidId", - "ext": { - "stype": "ppuid" - } - } - ] - } - ] - } - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "video": { - "placement": 3, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "data": { - "adserver": { - "name": "gam", - "adslot": "someAdSlot" - }, - "dataAttr1": "dataVal1", - "dataAttr2": "dataVal2" - }, - "bidder": { - "video": { - }, - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "uri?tk_xint=pbs-test-tracker", - "body": { - "id": "test-request-id", - "device": { - "ext": { - "rp": { - "pixelratio": 0 - } - }, - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - } - }, - "user": { - "buyeruid": "buyeruid", - "data": [ - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy" - } - ] - }, - { - "ext": { - "segtax": "someValue" - }, - "segment": [ - { - "id": "shouldNotBeCopied" - } - ] - }, - { - "ext": { - "segtax": "4" - }, - "segment": [ - { - "id": "shouldNotBeCopied2" - } - ] - }, - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy2" - } - ] - }, - { - "ext": { - "segtax": [ - 4 - ] - }, - "segment": [ - { - "id": "shouldNotBeCopied3" - } - ] - } - ], - "ext": { - "rp": { - "target": { - "someKey": "someValue", - "dataKey1": [ - "dataValue1" - ], - "dataKey2": [ - "dataValue2", - "dataValue3" - ], - "dataKey3": [ - "true" - ], - "iab": [ - "idToCopy", - "idToCopy2" - ] - } - }, - "eids": [ - { - "source": "rubiconproject.com", - "uids": [ - { - "id": "uidId", - "ext": { - "stype": "ppuid" - } - } - ] - } - ] - }, - "keywords": "someKeywords" - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "secure": 1, - "video": { - "placement": 3, - "ext": { - "rp": { - "size_id": 203 - } - }, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "rp": { - "target": { - "dataAttr1": [ - "dataVal1" - ], - "dataAttr2": [ - "dataVal2" - ], - "page": [ - "somePage" - ], - "ref": [ - "someRef" - ], - "search": [ - "someSearch" - ], - "dfp_ad_unit_code": "someAdSlot" - }, - "track": { - "mint": "", - "mint_version": "" - }, - "zone_id": 535510 - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - } - ], - "seat": "adman" - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - }, - "type": "video" - } - ] - } - ] -} From 061aa844459befc5d8950f0e7d8ab7c86af95fe2 Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Fri, 29 Sep 2023 16:13:59 +0530 Subject: [PATCH 028/138] Adapter alias - syncer changes (#3082) co-authored by @onkarvhanumante --- config/bidderinfo.go | 9 ++++-- config/bidderinfo_test.go | 63 ++++++++++++++++++++++++++++++--------- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/config/bidderinfo.go b/config/bidderinfo.go index 0027e4d21d0..3b3f86d4ec9 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -321,8 +321,13 @@ func processBidderAliases(aliasNillableFieldsByBidder map[string]aliasNillableFi if aliasBidderInfo.PlatformID == "" { aliasBidderInfo.PlatformID = parentBidderInfo.PlatformID } - if aliasBidderInfo.Syncer == nil { - aliasBidderInfo.Syncer = parentBidderInfo.Syncer + if aliasBidderInfo.Syncer == nil && parentBidderInfo.Syncer != nil { + syncerKey := aliasBidderInfo.AliasOf + if parentBidderInfo.Syncer.Key != "" { + syncerKey = parentBidderInfo.Syncer.Key + } + syncer := Syncer{Key: syncerKey} + aliasBidderInfo.Syncer = &syncer } if aliasBidderInfo.UserSyncURL == "" { aliasBidderInfo.UserSyncURL = parentBidderInfo.UserSyncURL diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go index 438ed5af776..0900421a574 100644 --- a/config/bidderinfo_test.go +++ b/config/bidderinfo_test.go @@ -262,12 +262,6 @@ func TestProcessBidderInfo(t *testing.T) { PlatformID: "123", Syncer: &Syncer{ Key: "foo", - IFrame: &SyncerEndpoint{ - URL: "https://foo.com/sync?mode=iframe&r={{.RedirectURL}}", - RedirectURL: "https://redirect/setuid/iframe", - ExternalURL: "https://iframe.host", - UserMacro: "UID", - }, }, UserSyncURL: "user-url", XAPI: AdapterXAPI{ @@ -293,7 +287,7 @@ func TestProcessBidderInfo(t *testing.T) { } func TestProcessAliasBidderInfo(t *testing.T) { - parentBidderInfo := BidderInfo{ + parentWithSyncerKey := BidderInfo{ AppSecret: "app-secret", Capabilities: &CapabilitiesInfo{ App: &PlatformInfo{ @@ -389,8 +383,29 @@ func TestProcessAliasBidderInfo(t *testing.T) { Tracker: "alias-tracker", }, } - bidderB := parentBidderInfo + bidderB := parentWithSyncerKey bidderB.AliasOf = "bidderA" + bidderB.Syncer = &Syncer{ + Key: bidderB.Syncer.Key, + } + + parentWithoutSyncerKey := BidderInfo{ + Syncer: &Syncer{ + IFrame: &SyncerEndpoint{ + URL: "https://foo.com/sync?mode=iframe&r={{.RedirectURL}}", + RedirectURL: "https://redirect/setuid/iframe", + ExternalURL: "https://iframe.host", + UserMacro: "UID", + }, + }, + } + + bidderC := parentWithoutSyncerKey + bidderC.AliasOf = "bidderA" + bidderC.Syncer = &Syncer{ + Key: "bidderA", + } + testCases := []struct { description string aliasInfos map[string]aliasNillableFields @@ -399,7 +414,7 @@ func TestProcessAliasBidderInfo(t *testing.T) { expectedErr error }{ { - description: "inherit all parent info in alias bidder", + description: "inherit all parent info in alias bidder, use parent syncer key as syncer alias key", aliasInfos: map[string]aliasNillableFields{ "bidderB": { Disabled: nil, @@ -409,14 +424,34 @@ func TestProcessAliasBidderInfo(t *testing.T) { }, }, bidderInfos: BidderInfos{ - "bidderA": parentBidderInfo, + "bidderA": parentWithSyncerKey, "bidderB": BidderInfo{ AliasOf: "bidderA", // all other fields should be inherited from parent bidder }, }, expectedErr: nil, - expectedBidderInfos: BidderInfos{"bidderA": parentBidderInfo, "bidderB": bidderB}, + expectedBidderInfos: BidderInfos{"bidderA": parentWithSyncerKey, "bidderB": bidderB}, + }, + { + description: "inherit all parent info in alias bidder, use parent name as syncer alias key", + aliasInfos: map[string]aliasNillableFields{ + "bidderC": { + Disabled: nil, + ModifyingVastXmlAllowed: nil, + Experiment: nil, + XAPI: nil, + }, + }, + bidderInfos: BidderInfos{ + "bidderA": parentWithoutSyncerKey, + "bidderC": BidderInfo{ + AliasOf: "bidderA", + // all other fields should be inherited from parent bidder + }, + }, + expectedErr: nil, + expectedBidderInfos: BidderInfos{"bidderA": parentWithoutSyncerKey, "bidderC": bidderC}, }, { description: "all bidder info specified for alias, do not inherit from parent bidder", @@ -429,11 +464,11 @@ func TestProcessAliasBidderInfo(t *testing.T) { }, }, bidderInfos: BidderInfos{ - "bidderA": parentBidderInfo, + "bidderA": parentWithSyncerKey, "bidderB": aliasBidderInfo, }, expectedErr: nil, - expectedBidderInfos: BidderInfos{"bidderA": parentBidderInfo, "bidderB": aliasBidderInfo}, + expectedBidderInfos: BidderInfos{"bidderA": parentWithSyncerKey, "bidderB": aliasBidderInfo}, }, { description: "invalid alias", @@ -461,7 +496,7 @@ func TestProcessAliasBidderInfo(t *testing.T) { if test.expectedErr != nil { assert.Equal(t, test.expectedErr, err) } else { - assert.Equal(t, test.expectedBidderInfos, bidderInfos) + assert.Equal(t, test.expectedBidderInfos, bidderInfos, test.description) } } } From 1a5f12c5bc104981ef16be65c88572cfef2517c0 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Fri, 29 Sep 2023 11:29:43 -0400 Subject: [PATCH 029/138] Remove Config Backwards Compatibility: Host GDPR Options (#3153) --- config/config.go | 98 ------ config/config_test.go | 766 ------------------------------------------ 2 files changed, 864 deletions(-) diff --git a/config/config.go b/config/config.go index b083719e956..a8d276a15c2 100644 --- a/config/config.go +++ b/config/config.go @@ -6,7 +6,6 @@ import ( "fmt" "net/url" "reflect" - "strconv" "strings" "time" @@ -409,7 +408,6 @@ func (t *TCF2) PurposeOneTreatmentAccessAllowed() bool { // Making a purpose struct so purpose specific details can be added later. type TCF2Purpose struct { - Enabled bool `mapstructure:"enabled"` // Deprecated: Use enforce_purpose instead EnforceAlgo string `mapstructure:"enforce_algo"` // Integer representation of enforcement algo for performance improvement on compares EnforceAlgoID TCF2EnforcementAlgo @@ -1117,24 +1115,9 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.AutomaticEnv() v.ReadInConfig() - // Migrate config settings to maintain compatibility with old configs - migrateConfigPurposeOneTreatment(v) - migrateConfigSpecialFeature1(v) - migrateConfigTCF2PurposeFlags(v) - // These defaults must be set after the migrate functions because those functions look for the presence of these // config fields and there isn't a way to detect presence of a config field using the viper package if a default // is set. Viper IsSet and Get functions consider default values. - v.SetDefault("gdpr.tcf2.purpose1.enabled", true) - v.SetDefault("gdpr.tcf2.purpose2.enabled", true) - v.SetDefault("gdpr.tcf2.purpose3.enabled", true) - v.SetDefault("gdpr.tcf2.purpose4.enabled", true) - v.SetDefault("gdpr.tcf2.purpose5.enabled", true) - v.SetDefault("gdpr.tcf2.purpose6.enabled", true) - v.SetDefault("gdpr.tcf2.purpose7.enabled", true) - v.SetDefault("gdpr.tcf2.purpose8.enabled", true) - v.SetDefault("gdpr.tcf2.purpose9.enabled", true) - v.SetDefault("gdpr.tcf2.purpose10.enabled", true) v.SetDefault("gdpr.tcf2.purpose1.enforce_algo", TCF2EnforceAlgoFull) v.SetDefault("gdpr.tcf2.purpose2.enforce_algo", TCF2EnforceAlgoFull) v.SetDefault("gdpr.tcf2.purpose3.enforce_algo", TCF2EnforceAlgoFull) @@ -1180,87 +1163,6 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { } } -func migrateConfigPurposeOneTreatment(v *viper.Viper) { - if oldConfig, ok := v.Get("gdpr.tcf2.purpose_one_treatement").(map[string]interface{}); ok { - if v.IsSet("gdpr.tcf2.purpose_one_treatment") { - glog.Warning("using gdpr.tcf2.purpose_one_treatment and ignoring deprecated gdpr.tcf2.purpose_one_treatement") - } else { - glog.Warning("gdpr.tcf2.purpose_one_treatement.enabled should be changed to gdpr.tcf2.purpose_one_treatment.enabled") - glog.Warning("gdpr.tcf2.purpose_one_treatement.access_allowed should be changed to gdpr.tcf2.purpose_one_treatment.access_allowed") - v.Set("gdpr.tcf2.purpose_one_treatment", oldConfig) - } - } -} - -func migrateConfigSpecialFeature1(v *viper.Viper) { - if oldConfig, ok := v.Get("gdpr.tcf2.special_purpose1").(map[string]interface{}); ok { - if v.IsSet("gdpr.tcf2.special_feature1") { - glog.Warning("using gdpr.tcf2.special_feature1 and ignoring deprecated gdpr.tcf2.special_purpose1") - } else { - glog.Warning("gdpr.tcf2.special_purpose1.enabled is deprecated and should be changed to gdpr.tcf2.special_feature1.enforce") - glog.Warning("gdpr.tcf2.special_purpose1.vendor_exceptions is deprecated and should be changed to gdpr.tcf2.special_feature1.vendor_exceptions") - v.Set("gdpr.tcf2.special_feature1.enforce", oldConfig["enabled"]) - v.Set("gdpr.tcf2.special_feature1.vendor_exceptions", oldConfig["vendor_exceptions"]) - } - } -} - -func migrateConfigTCF2PurposeFlags(v *viper.Viper) { - migrateConfigTCF2EnforcePurposeFlags(v) - migrateConfigTCF2PurposeEnabledFlags(v) -} - -func migrateConfigTCF2EnforcePurposeFlags(v *viper.Viper) { - for i := 1; i <= 10; i++ { - algoField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_algo", i) - purposeField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_purpose", i) - - if !v.IsSet(purposeField) { - continue - } - if _, ok := v.Get(purposeField).(string); !ok { - continue - } - if v.IsSet(algoField) { - glog.Warningf("using %s and ignoring deprecated %s string type", algoField, purposeField) - } else { - v.Set(algoField, TCF2EnforceAlgoFull) - - glog.Warningf("setting %s to \"%s\" based on deprecated %s string type \"%s\"", algoField, TCF2EnforceAlgoFull, purposeField, v.GetString(purposeField)) - } - - oldPurposeFieldValue := v.GetString(purposeField) - newPurposeFieldValue := "false" - if oldPurposeFieldValue == TCF2EnforceAlgoFull { - newPurposeFieldValue = "true" - } - - glog.Warningf("converting %s from string \"%s\" to bool \"%s\"; string type is deprecated", purposeField, oldPurposeFieldValue, newPurposeFieldValue) - v.Set(purposeField, newPurposeFieldValue) - } -} - -func migrateConfigTCF2PurposeEnabledFlags(v *viper.Viper) { - for i := 1; i <= 10; i++ { - oldField := fmt.Sprintf("gdpr.tcf2.purpose%d.enabled", i) - newField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_purpose", i) - - if v.IsSet(oldField) { - oldConfig := v.GetBool(oldField) - if v.IsSet(newField) { - glog.Warningf("using %s and ignoring deprecated %s", newField, oldField) - } else { - glog.Warningf("%s is deprecated and should be changed to %s", oldField, newField) - v.Set(newField, oldConfig) - } - } - - if v.IsSet(newField) { - v.Set(oldField, strconv.FormatBool(v.GetBool(newField))) - } - } -} - func isConfigInfoPresent(v *viper.Viper, prefix string, fields []string) bool { prefix = prefix + "." for _, field := range fields { diff --git a/config/config_test.go b/config/config_test.go index 9fc85715c6a..97be28eb6e3 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -208,7 +208,6 @@ func TestDefaults(t *testing.T) { expectedTCF2 := TCF2{ Enabled: true, Purpose1: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -217,7 +216,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose2: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -226,7 +224,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose3: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -235,7 +232,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose4: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -244,7 +240,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose5: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -253,7 +248,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose6: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -262,7 +256,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose7: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -271,7 +264,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose8: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -280,7 +272,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose9: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -289,7 +280,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose10: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -334,7 +324,6 @@ gdpr: enforce_vendors: false vendor_exceptions: ["foo1a", "foo1b"] purpose2: - enabled: false enforce_algo: "full" enforce_purpose: false enforce_vendors: false @@ -608,7 +597,6 @@ func TestFullConfig(t *testing.T) { expectedTCF2 := TCF2{ Enabled: true, Purpose1: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -617,7 +605,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo1a"): {}, openrtb_ext.BidderName("foo1b"): {}}, }, Purpose2: TCF2Purpose{ - Enabled: false, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: false, @@ -626,7 +613,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo2"): {}}, }, Purpose3: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoBasic, EnforceAlgoID: TCF2BasicEnforcement, EnforcePurpose: true, @@ -635,7 +621,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo3"): {}}, }, Purpose4: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -644,7 +629,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo4"): {}}, }, Purpose5: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -653,7 +637,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo5"): {}}, }, Purpose6: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -662,7 +645,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo6"): {}}, }, Purpose7: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -671,7 +653,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo7"): {}}, }, Purpose8: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -680,7 +661,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo8"): {}}, }, Purpose9: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -689,7 +669,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo9"): {}}, }, Purpose10: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -943,751 +922,6 @@ func TestBidderInfoFromEnv(t *testing.T) { assert.Equal(t, "2.6", cfg.BidderInfos["bidder1"].OpenRTB.Version) } -func TestMigrateConfigPurposeOneTreatment(t *testing.T) { - oldPurposeOneTreatmentConfig := []byte(` - gdpr: - tcf2: - purpose_one_treatement: - enabled: true - access_allowed: true - `) - newPurposeOneTreatmentConfig := []byte(` - gdpr: - tcf2: - purpose_one_treatment: - enabled: true - access_allowed: true - `) - oldAndNewPurposeOneTreatmentConfig := []byte(` - gdpr: - tcf2: - purpose_one_treatement: - enabled: false - access_allowed: true - purpose_one_treatment: - enabled: true - access_allowed: false - `) - - tests := []struct { - description string - config []byte - wantPurpose1TreatmentEnabled bool - wantPurpose1TreatmentAccessAllowed bool - }{ - { - description: "New config and old config not set", - config: []byte{}, - }, - { - description: "New config not set, old config set", - config: oldPurposeOneTreatmentConfig, - wantPurpose1TreatmentEnabled: true, - wantPurpose1TreatmentAccessAllowed: true, - }, - { - description: "New config set, old config not set", - config: newPurposeOneTreatmentConfig, - wantPurpose1TreatmentEnabled: true, - wantPurpose1TreatmentAccessAllowed: true, - }, - { - description: "New config and old config set", - config: oldAndNewPurposeOneTreatmentConfig, - wantPurpose1TreatmentEnabled: true, - wantPurpose1TreatmentAccessAllowed: false, - }, - } - - for _, tt := range tests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigPurposeOneTreatment(v) - - if len(tt.config) > 0 { - assert.Equal(t, tt.wantPurpose1TreatmentEnabled, v.Get("gdpr.tcf2.purpose_one_treatment.enabled").(bool), tt.description) - assert.Equal(t, tt.wantPurpose1TreatmentAccessAllowed, v.Get("gdpr.tcf2.purpose_one_treatment.access_allowed").(bool), tt.description) - } else { - assert.Nil(t, v.Get("gdpr.tcf2.purpose_one_treatment.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose_one_treatment.access_allowed"), tt.description) - } - } -} - -func TestMigrateConfigSpecialFeature1(t *testing.T) { - oldSpecialFeature1Config := []byte(` - gdpr: - tcf2: - special_purpose1: - enabled: true - vendor_exceptions: ["appnexus"] - `) - newSpecialFeature1Config := []byte(` - gdpr: - tcf2: - special_feature1: - enforce: true - vendor_exceptions: ["appnexus"] - `) - oldAndNewSpecialFeature1Config := []byte(` - gdpr: - tcf2: - special_purpose1: - enabled: false - vendor_exceptions: ["appnexus"] - special_feature1: - enforce: true - vendor_exceptions: ["rubicon"] - `) - - tests := []struct { - description string - config []byte - wantSpecialFeature1Enforce bool - wantSpecialFeature1VendorExceptions []string - }{ - { - description: "New config and old config not set", - config: []byte{}, - }, - { - description: "New config not set, old config set", - config: oldSpecialFeature1Config, - wantSpecialFeature1Enforce: true, - wantSpecialFeature1VendorExceptions: []string{"appnexus"}, - }, - { - description: "New config set, old config not set", - config: newSpecialFeature1Config, - wantSpecialFeature1Enforce: true, - wantSpecialFeature1VendorExceptions: []string{"appnexus"}, - }, - { - description: "New config and old config set", - config: oldAndNewSpecialFeature1Config, - wantSpecialFeature1Enforce: true, - wantSpecialFeature1VendorExceptions: []string{"rubicon"}, - }, - } - - for _, tt := range tests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigSpecialFeature1(v) - - if len(tt.config) > 0 { - assert.Equal(t, tt.wantSpecialFeature1Enforce, v.Get("gdpr.tcf2.special_feature1.enforce").(bool), tt.description) - assert.Equal(t, tt.wantSpecialFeature1VendorExceptions, v.GetStringSlice("gdpr.tcf2.special_feature1.vendor_exceptions"), tt.description) - } else { - assert.Nil(t, v.Get("gdpr.tcf2.special_feature1.enforce"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.special_feature1.vendor_exceptions"), tt.description) - } - - var c Configuration - err := v.Unmarshal(&c) - assert.NoError(t, err, tt.description) - assert.Equal(t, tt.wantSpecialFeature1Enforce, c.GDPR.TCF2.SpecialFeature1.Enforce, tt.description) - - // convert expected vendor exceptions to type BidderName - expectedVendorExceptions := make([]openrtb_ext.BidderName, 0, 0) - for _, ve := range tt.wantSpecialFeature1VendorExceptions { - expectedVendorExceptions = append(expectedVendorExceptions, openrtb_ext.BidderName(ve)) - } - assert.ElementsMatch(t, expectedVendorExceptions, c.GDPR.TCF2.SpecialFeature1.VendorExceptions, tt.description) - } -} - -func TestMigrateConfigTCF2PurposeEnabledFlags(t *testing.T) { - trueStr := "true" - falseStr := "false" - - tests := []struct { - description string - config []byte - wantPurpose1EnforcePurpose string - wantPurpose2EnforcePurpose string - wantPurpose3EnforcePurpose string - wantPurpose4EnforcePurpose string - wantPurpose5EnforcePurpose string - wantPurpose6EnforcePurpose string - wantPurpose7EnforcePurpose string - wantPurpose8EnforcePurpose string - wantPurpose9EnforcePurpose string - wantPurpose10EnforcePurpose string - wantPurpose1Enabled string - wantPurpose2Enabled string - wantPurpose3Enabled string - wantPurpose4Enabled string - wantPurpose5Enabled string - wantPurpose6Enabled string - wantPurpose7Enabled string - wantPurpose8Enabled string - wantPurpose9Enabled string - wantPurpose10Enabled string - }{ - { - description: "New config and old config flags not set", - config: []byte{}, - }, - { - description: "New config not set, old config set - use old flags", - config: []byte(` - gdpr: - tcf2: - purpose1: - enabled: false - purpose2: - enabled: true - purpose3: - enabled: false - purpose4: - enabled: true - purpose5: - enabled: false - purpose6: - enabled: true - purpose7: - enabled: false - purpose8: - enabled: true - purpose9: - enabled: false - purpose10: - enabled: true - `), - wantPurpose1EnforcePurpose: falseStr, - wantPurpose2EnforcePurpose: trueStr, - wantPurpose3EnforcePurpose: falseStr, - wantPurpose4EnforcePurpose: trueStr, - wantPurpose5EnforcePurpose: falseStr, - wantPurpose6EnforcePurpose: trueStr, - wantPurpose7EnforcePurpose: falseStr, - wantPurpose8EnforcePurpose: trueStr, - wantPurpose9EnforcePurpose: falseStr, - wantPurpose10EnforcePurpose: trueStr, - wantPurpose1Enabled: falseStr, - wantPurpose2Enabled: trueStr, - wantPurpose3Enabled: falseStr, - wantPurpose4Enabled: trueStr, - wantPurpose5Enabled: falseStr, - wantPurpose6Enabled: trueStr, - wantPurpose7Enabled: falseStr, - wantPurpose8Enabled: trueStr, - wantPurpose9Enabled: falseStr, - wantPurpose10Enabled: trueStr, - }, - { - description: "New config flags set, old config flags not set - use new flags", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_purpose: true - purpose2: - enforce_purpose: false - purpose3: - enforce_purpose: true - purpose4: - enforce_purpose: false - purpose5: - enforce_purpose: true - purpose6: - enforce_purpose: false - purpose7: - enforce_purpose: true - purpose8: - enforce_purpose: false - purpose9: - enforce_purpose: true - purpose10: - enforce_purpose: false - `), - wantPurpose1EnforcePurpose: trueStr, - wantPurpose2EnforcePurpose: falseStr, - wantPurpose3EnforcePurpose: trueStr, - wantPurpose4EnforcePurpose: falseStr, - wantPurpose5EnforcePurpose: trueStr, - wantPurpose6EnforcePurpose: falseStr, - wantPurpose7EnforcePurpose: trueStr, - wantPurpose8EnforcePurpose: falseStr, - wantPurpose9EnforcePurpose: trueStr, - wantPurpose10EnforcePurpose: falseStr, - wantPurpose1Enabled: trueStr, - wantPurpose2Enabled: falseStr, - wantPurpose3Enabled: trueStr, - wantPurpose4Enabled: falseStr, - wantPurpose5Enabled: trueStr, - wantPurpose6Enabled: falseStr, - wantPurpose7Enabled: trueStr, - wantPurpose8Enabled: falseStr, - wantPurpose9Enabled: trueStr, - wantPurpose10Enabled: falseStr, - }, - { - description: "New config flags and old config flags set - use new flags", - config: []byte(` - gdpr: - tcf2: - purpose1: - enabled: false - enforce_purpose: true - purpose2: - enabled: false - enforce_purpose: true - purpose3: - enabled: false - enforce_purpose: true - purpose4: - enabled: false - enforce_purpose: true - purpose5: - enabled: false - enforce_purpose: true - purpose6: - enabled: false - enforce_purpose: true - purpose7: - enabled: false - enforce_purpose: true - purpose8: - enabled: false - enforce_purpose: true - purpose9: - enabled: false - enforce_purpose: true - purpose10: - enabled: false - enforce_purpose: true - `), - wantPurpose1EnforcePurpose: trueStr, - wantPurpose2EnforcePurpose: trueStr, - wantPurpose3EnforcePurpose: trueStr, - wantPurpose4EnforcePurpose: trueStr, - wantPurpose5EnforcePurpose: trueStr, - wantPurpose6EnforcePurpose: trueStr, - wantPurpose7EnforcePurpose: trueStr, - wantPurpose8EnforcePurpose: trueStr, - wantPurpose9EnforcePurpose: trueStr, - wantPurpose10EnforcePurpose: trueStr, - wantPurpose1Enabled: trueStr, - wantPurpose2Enabled: trueStr, - wantPurpose3Enabled: trueStr, - wantPurpose4Enabled: trueStr, - wantPurpose5Enabled: trueStr, - wantPurpose6Enabled: trueStr, - wantPurpose7Enabled: trueStr, - wantPurpose8Enabled: trueStr, - wantPurpose9Enabled: trueStr, - wantPurpose10Enabled: trueStr, - }, - } - - for _, tt := range tests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigTCF2PurposeEnabledFlags(v) - - if len(tt.config) > 0 { - assert.Equal(t, tt.wantPurpose1EnforcePurpose, v.GetString("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose2EnforcePurpose, v.GetString("gdpr.tcf2.purpose2.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose3EnforcePurpose, v.GetString("gdpr.tcf2.purpose3.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose4EnforcePurpose, v.GetString("gdpr.tcf2.purpose4.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose5EnforcePurpose, v.GetString("gdpr.tcf2.purpose5.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose6EnforcePurpose, v.GetString("gdpr.tcf2.purpose6.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose7EnforcePurpose, v.GetString("gdpr.tcf2.purpose7.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose8EnforcePurpose, v.GetString("gdpr.tcf2.purpose8.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose9EnforcePurpose, v.GetString("gdpr.tcf2.purpose9.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose10EnforcePurpose, v.GetString("gdpr.tcf2.purpose10.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose1Enabled, v.GetString("gdpr.tcf2.purpose1.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose2Enabled, v.GetString("gdpr.tcf2.purpose2.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose3Enabled, v.GetString("gdpr.tcf2.purpose3.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose4Enabled, v.GetString("gdpr.tcf2.purpose4.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose5Enabled, v.GetString("gdpr.tcf2.purpose5.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose6Enabled, v.GetString("gdpr.tcf2.purpose6.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose7Enabled, v.GetString("gdpr.tcf2.purpose7.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose8Enabled, v.GetString("gdpr.tcf2.purpose8.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose9Enabled, v.GetString("gdpr.tcf2.purpose9.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose10Enabled, v.GetString("gdpr.tcf2.purpose10.enabled"), tt.description) - } else { - assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enabled"), tt.description) - } - } -} - -func TestMigrateConfigTCF2PurposeFlags(t *testing.T) { - tests := []struct { - description string - config []byte - wantPurpose1EnforceAlgo string - wantPurpose1EnforcePurpose bool - wantPurpose1Enabled bool - }{ - { - description: "enforce_purpose does not set enforce_algo but sets enabled", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_algo: "off" - enforce_purpose: "full" - enabled: false - purpose2: - enforce_purpose: "full" - enabled: false - purpose3: - enabled: false - `), - wantPurpose1EnforceAlgo: "off", - wantPurpose1EnforcePurpose: true, - wantPurpose1Enabled: true, - }, - { - description: "enforce_purpose sets enforce_algo and enabled", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_purpose: "full" - enabled: false - `), - wantPurpose1EnforceAlgo: "full", - wantPurpose1EnforcePurpose: true, - wantPurpose1Enabled: true, - }, - { - description: "enforce_purpose does not set enforce_algo or enabled", - config: []byte(` - gdpr: - tcf2: - purpose1: - enabled: false - `), - wantPurpose1EnforceAlgo: "", - wantPurpose1EnforcePurpose: false, - wantPurpose1Enabled: false, - }, - } - - for _, tt := range tests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigTCF2PurposeFlags(v) - - assert.Equal(t, tt.wantPurpose1EnforceAlgo, v.GetString("gdpr.tcf2.purpose1.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose1EnforcePurpose, v.GetBool("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose1Enabled, v.GetBool("gdpr.tcf2.purpose1.enabled"), tt.description) - } - -} - -func TestMigrateConfigTCF2EnforcePurposeFlags(t *testing.T) { - trueStr := "true" - falseStr := "false" - - tests := []struct { - description string - config []byte - wantEnforceAlgosSet bool - wantPurpose1EnforceAlgo string - wantPurpose2EnforceAlgo string - wantPurpose3EnforceAlgo string - wantPurpose4EnforceAlgo string - wantPurpose5EnforceAlgo string - wantPurpose6EnforceAlgo string - wantPurpose7EnforceAlgo string - wantPurpose8EnforceAlgo string - wantPurpose9EnforceAlgo string - wantPurpose10EnforceAlgo string - wantEnforcePurposesSet bool - wantPurpose1EnforcePurpose string - wantPurpose2EnforcePurpose string - wantPurpose3EnforcePurpose string - wantPurpose4EnforcePurpose string - wantPurpose5EnforcePurpose string - wantPurpose6EnforcePurpose string - wantPurpose7EnforcePurpose string - wantPurpose8EnforcePurpose string - wantPurpose9EnforcePurpose string - wantPurpose10EnforcePurpose string - }{ - { - description: "enforce_algo and enforce_purpose are not set", - config: []byte{}, - wantEnforceAlgosSet: false, - wantEnforcePurposesSet: false, - }, - { - description: "enforce_algo not set; set it based on enforce_purpose string value", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_purpose: "full" - purpose2: - enforce_purpose: "no" - purpose3: - enforce_purpose: "full" - purpose4: - enforce_purpose: "no" - purpose5: - enforce_purpose: "full" - purpose6: - enforce_purpose: "no" - purpose7: - enforce_purpose: "full" - purpose8: - enforce_purpose: "no" - purpose9: - enforce_purpose: "full" - purpose10: - enforce_purpose: "no" - `), - wantEnforceAlgosSet: true, - wantPurpose1EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose2EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose3EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose4EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose5EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose6EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose7EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose8EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose9EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose10EnforceAlgo: TCF2EnforceAlgoFull, - wantEnforcePurposesSet: true, - wantPurpose1EnforcePurpose: trueStr, - wantPurpose2EnforcePurpose: falseStr, - wantPurpose3EnforcePurpose: trueStr, - wantPurpose4EnforcePurpose: falseStr, - wantPurpose5EnforcePurpose: trueStr, - wantPurpose6EnforcePurpose: falseStr, - wantPurpose7EnforcePurpose: trueStr, - wantPurpose8EnforcePurpose: falseStr, - wantPurpose9EnforcePurpose: trueStr, - wantPurpose10EnforcePurpose: falseStr, - }, - { - description: "enforce_algo not set; don't set it based on enforce_purpose bool value", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_purpose: true - purpose2: - enforce_purpose: false - purpose3: - enforce_purpose: true - purpose4: - enforce_purpose: false - purpose5: - enforce_purpose: true - purpose6: - enforce_purpose: false - purpose7: - enforce_purpose: true - purpose8: - enforce_purpose: false - purpose9: - enforce_purpose: true - purpose10: - enforce_purpose: false - `), - wantEnforceAlgosSet: false, - wantEnforcePurposesSet: true, - wantPurpose1EnforcePurpose: trueStr, - wantPurpose2EnforcePurpose: falseStr, - wantPurpose3EnforcePurpose: trueStr, - wantPurpose4EnforcePurpose: falseStr, - wantPurpose5EnforcePurpose: trueStr, - wantPurpose6EnforcePurpose: falseStr, - wantPurpose7EnforcePurpose: trueStr, - wantPurpose8EnforcePurpose: falseStr, - wantPurpose9EnforcePurpose: trueStr, - wantPurpose10EnforcePurpose: falseStr, - }, - { - description: "enforce_algo is set and enforce_purpose is not; enforce_algo is unchanged", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_algo: "full" - purpose2: - enforce_algo: "full" - purpose3: - enforce_algo: "full" - purpose4: - enforce_algo: "full" - purpose5: - enforce_algo: "full" - purpose6: - enforce_algo: "full" - purpose7: - enforce_algo: "full" - purpose8: - enforce_algo: "full" - purpose9: - enforce_algo: "full" - purpose10: - enforce_algo: "full" - `), - wantEnforceAlgosSet: true, - wantPurpose1EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose2EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose3EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose4EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose5EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose6EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose7EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose8EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose9EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose10EnforceAlgo: TCF2EnforceAlgoFull, - wantEnforcePurposesSet: false, - }, - { - description: "enforce_algo and enforce_purpose are set; enforce_algo is unchanged", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_algo: "full" - enforce_purpose: "no" - purpose2: - enforce_algo: "full" - enforce_purpose: "no" - purpose3: - enforce_algo: "full" - enforce_purpose: "no" - purpose4: - enforce_algo: "full" - enforce_purpose: "no" - purpose5: - enforce_algo: "full" - enforce_purpose: "no" - purpose6: - enforce_algo: "full" - enforce_purpose: "no" - purpose7: - enforce_algo: "full" - enforce_purpose: "no" - purpose8: - enforce_algo: "full" - enforce_purpose: "no" - purpose9: - enforce_algo: "full" - enforce_purpose: "no" - purpose10: - enforce_algo: "full" - enforce_purpose: "no" - `), - wantEnforceAlgosSet: true, - wantPurpose1EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose2EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose3EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose4EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose5EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose6EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose7EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose8EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose9EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose10EnforceAlgo: TCF2EnforceAlgoFull, - wantEnforcePurposesSet: true, - wantPurpose1EnforcePurpose: falseStr, - wantPurpose2EnforcePurpose: falseStr, - wantPurpose3EnforcePurpose: falseStr, - wantPurpose4EnforcePurpose: falseStr, - wantPurpose5EnforcePurpose: falseStr, - wantPurpose6EnforcePurpose: falseStr, - wantPurpose7EnforcePurpose: falseStr, - wantPurpose8EnforcePurpose: falseStr, - wantPurpose9EnforcePurpose: falseStr, - wantPurpose10EnforcePurpose: falseStr, - }, - } - - for _, tt := range tests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigTCF2EnforcePurposeFlags(v) - - if tt.wantEnforceAlgosSet { - assert.Equal(t, tt.wantPurpose1EnforceAlgo, v.GetString("gdpr.tcf2.purpose1.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose2EnforceAlgo, v.GetString("gdpr.tcf2.purpose2.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose3EnforceAlgo, v.GetString("gdpr.tcf2.purpose3.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose4EnforceAlgo, v.GetString("gdpr.tcf2.purpose4.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose5EnforceAlgo, v.GetString("gdpr.tcf2.purpose5.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose6EnforceAlgo, v.GetString("gdpr.tcf2.purpose6.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose7EnforceAlgo, v.GetString("gdpr.tcf2.purpose7.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose8EnforceAlgo, v.GetString("gdpr.tcf2.purpose8.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose9EnforceAlgo, v.GetString("gdpr.tcf2.purpose9.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose10EnforceAlgo, v.GetString("gdpr.tcf2.purpose10.enforce_algo"), tt.description) - } else { - assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enforce_algo"), tt.description) - } - - if tt.wantEnforcePurposesSet { - assert.Equal(t, tt.wantPurpose1EnforcePurpose, v.GetString("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose2EnforcePurpose, v.GetString("gdpr.tcf2.purpose2.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose3EnforcePurpose, v.GetString("gdpr.tcf2.purpose3.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose4EnforcePurpose, v.GetString("gdpr.tcf2.purpose4.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose5EnforcePurpose, v.GetString("gdpr.tcf2.purpose5.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose6EnforcePurpose, v.GetString("gdpr.tcf2.purpose6.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose7EnforcePurpose, v.GetString("gdpr.tcf2.purpose7.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose8EnforcePurpose, v.GetString("gdpr.tcf2.purpose8.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose9EnforcePurpose, v.GetString("gdpr.tcf2.purpose9.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose10EnforcePurpose, v.GetString("gdpr.tcf2.purpose10.enforce_purpose"), tt.description) - } else { - assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enforce_purpose"), tt.description) - } - } -} - func TestIsConfigInfoPresent(t *testing.T) { configPrefix1Field2Only := []byte(` prefix1: From 5cc92ae811966b22b485aaa5a8040a454ccfbd66 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Fri, 29 Sep 2023 11:57:59 -0400 Subject: [PATCH 030/138] Remove Config Backwards Compatibility: Account GDPR Purpose (#3154) --- account/account.go | 104 +--------- account/account_test.go | 284 +------------------------- metrics/config/metrics.go | 8 - metrics/go_metrics.go | 54 +---- metrics/go_metrics_test.go | 88 -------- metrics/metrics.go | 1 - metrics/metrics_mock.go | 4 - metrics/prometheus/prometheus.go | 74 +------ metrics/prometheus/prometheus_test.go | 85 -------- 9 files changed, 18 insertions(+), 684 deletions(-) diff --git a/account/account.go b/account/account.go index 36b6da51c38..7b5f9435a11 100644 --- a/account/account.go +++ b/account/account.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/buger/jsonparser" "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -12,7 +11,6 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/util/iputil" - jsonpatch "gopkg.in/evanphx/json-patch.v4" ) // GetAccount looks up the config.Account object referenced by the given accountID, with access rules applied @@ -50,23 +48,10 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r // accountID resolved to a valid account, merge with AccountDefaults for a complete config account = &config.Account{} err := json.Unmarshal(accountJSON, account) - - // this logic exists for backwards compatibility. If the initial unmarshal fails above, we attempt to - // resolve it by converting the GDPR enforce purpose fields and then attempting an unmarshal again before - // declaring a malformed account error. - // unmarshal fetched account to determine if it is well-formed - var deprecatedPurposeFields []string if _, ok := err.(*json.UnmarshalTypeError); ok { - // attempt to convert deprecated GDPR enforce purpose fields to resolve issue - accountJSON, err, deprecatedPurposeFields = ConvertGDPREnforcePurposeFields(accountJSON) - // unmarshal again to check if unmarshal error still exists after GDPR field conversion - err = json.Unmarshal(accountJSON, account) - - if _, ok := err.(*json.UnmarshalTypeError); ok { - return nil, []error{&errortypes.MalformedAcct{ - Message: fmt.Sprintf("The prebid-server account config for account id \"%s\" is malformed. Please reach out to the prebid server host.", accountID), - }} - } + return nil, []error{&errortypes.MalformedAcct{ + Message: fmt.Sprintf("The prebid-server account config for account id \"%s\" is malformed. Please reach out to the prebid server host.", accountID), + }} } usingGDPRChannelEnabled := useGDPRChannelEnabled(account) usingCCPAChannelEnabled := useCCPAChannelEnabled(account) @@ -77,10 +62,7 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r if usingCCPAChannelEnabled { me.RecordAccountCCPAChannelEnabledWarning(accountID) } - for _, purposeName := range deprecatedPurposeFields { - me.RecordAccountGDPRPurposeWarning(accountID, purposeName) - } - if len(deprecatedPurposeFields) > 0 || usingGDPRChannelEnabled || usingCCPAChannelEnabled { + if usingGDPRChannelEnabled || usingCCPAChannelEnabled { me.RecordAccountUpgradeStatus(accountID) } @@ -176,84 +158,6 @@ func setDerivedConfig(account *config.Account) { } } -// PatchAccount represents the GDPR portion of a publisher account configuration that can be mutated -// for backwards compatibility reasons -type PatchAccount struct { - GDPR map[string]*PatchAccountGDPRPurpose `json:"gdpr"` -} - -// PatchAccountGDPRPurpose represents account-specific GDPR purpose configuration data that can be mutated -// for backwards compatibility reasons -type PatchAccountGDPRPurpose struct { - EnforceAlgo string `json:"enforce_algo,omitempty"` - EnforcePurpose *bool `json:"enforce_purpose,omitempty"` -} - -// ConvertGDPREnforcePurposeFields is responsible for ensuring account GDPR config backwards compatibility -// given the recent type change of gdpr.purpose{1-10}.enforce_purpose from a string to a bool. This function -// iterates over each GDPR purpose config and sets enforce_purpose and enforce_algo to the appropriate -// bool and string values respectively if enforce_purpose is a string and enforce_algo is not set -func ConvertGDPREnforcePurposeFields(config []byte) (newConfig []byte, err error, deprecatedPurposeFields []string) { - gdprJSON, _, _, err := jsonparser.Get(config, "gdpr") - if err != nil && err == jsonparser.KeyPathNotFoundError { - return config, nil, deprecatedPurposeFields - } - if err != nil { - return nil, err, deprecatedPurposeFields - } - - newAccount := PatchAccount{ - GDPR: map[string]*PatchAccountGDPRPurpose{}, - } - - for i := 1; i <= 10; i++ { - purposeName := fmt.Sprintf("purpose%d", i) - - enforcePurpose, purposeDataType, _, err := jsonparser.Get(gdprJSON, purposeName, "enforce_purpose") - if err != nil && err == jsonparser.KeyPathNotFoundError { - continue - } - if err != nil { - return nil, err, deprecatedPurposeFields - } - if purposeDataType != jsonparser.String { - continue - } else { - deprecatedPurposeFields = append(deprecatedPurposeFields, purposeName) - } - - _, _, _, err = jsonparser.Get(gdprJSON, purposeName, "enforce_algo") - if err != nil && err != jsonparser.KeyPathNotFoundError { - return nil, err, deprecatedPurposeFields - } - if err == nil { - continue - } - - newEnforcePurpose := false - if string(enforcePurpose) == "full" { - newEnforcePurpose = true - } - - newAccount.GDPR[purposeName] = &PatchAccountGDPRPurpose{ - EnforceAlgo: "full", - EnforcePurpose: &newEnforcePurpose, - } - } - - patchConfig, err := json.Marshal(newAccount) - if err != nil { - return nil, err, deprecatedPurposeFields - } - - newConfig, err = jsonpatch.MergePatch(config, patchConfig) - if err != nil { - return nil, err, deprecatedPurposeFields - } - - return newConfig, nil, deprecatedPurposeFields -} - func useGDPRChannelEnabled(account *config.Account) bool { return account.GDPR.ChannelEnabled.IsSet() && !account.GDPR.IntegrationEnabled.IsSet() } diff --git a/account/account_test.go b/account/account_test.go index b527dee149f..d5223f76119 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -6,7 +6,6 @@ import ( "fmt" "testing" - "github.com/buger/jsonparser" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/metrics" @@ -18,23 +17,12 @@ import ( ) var mockAccountData = map[string]json.RawMessage{ - "valid_acct": json.RawMessage(`{"disabled":false}`), - "invalid_acct_ipv6_ipv4": json.RawMessage(`{"disabled":false, "privacy": {"ipv6": {"anon_keep_bits": -32}, "ipv4": {"anon_keep_bits": -16}}}`), - "disabled_acct": json.RawMessage(`{"disabled":true}`), - "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), - "gdpr_channel_enabled_acct": json.RawMessage(`{"disabled":false,"gdpr":{"channel_enabled":{"amp":true}}}`), - "ccpa_channel_enabled_acct": json.RawMessage(`{"disabled":false,"ccpa":{"channel_enabled":{"amp":true}}}`), - "gdpr_channel_enabled_deprecated_purpose_acct": json.RawMessage(`{"disabled":false,"gdpr":{"purpose1":{"enforce_purpose":"full"}, "channel_enabled":{"amp":true}}}`), - "gdpr_deprecated_purpose1": json.RawMessage(`{"disabled":false,"gdpr":{"purpose1":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose2": json.RawMessage(`{"disabled":false,"gdpr":{"purpose2":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose3": json.RawMessage(`{"disabled":false,"gdpr":{"purpose3":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose4": json.RawMessage(`{"disabled":false,"gdpr":{"purpose4":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose5": json.RawMessage(`{"disabled":false,"gdpr":{"purpose5":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose6": json.RawMessage(`{"disabled":false,"gdpr":{"purpose6":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose7": json.RawMessage(`{"disabled":false,"gdpr":{"purpose7":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose8": json.RawMessage(`{"disabled":false,"gdpr":{"purpose8":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose9": json.RawMessage(`{"disabled":false,"gdpr":{"purpose9":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose10": json.RawMessage(`{"disabled":false,"gdpr":{"purpose10":{"enforce_purpose":"full"}}}`), + "valid_acct": json.RawMessage(`{"disabled":false}`), + "invalid_acct_ipv6_ipv4": json.RawMessage(`{"disabled":false, "privacy": {"ipv6": {"anon_keep_bits": -32}, "ipv4": {"anon_keep_bits": -16}}}`), + "disabled_acct": json.RawMessage(`{"disabled":true}`), + "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), + "gdpr_channel_enabled_acct": json.RawMessage(`{"disabled":false,"gdpr":{"channel_enabled":{"amp":true}}}`), + "ccpa_channel_enabled_acct": json.RawMessage(`{"disabled":false,"ccpa":{"channel_enabled":{"amp":true}}}`), } type mockAccountFetcher struct { @@ -89,12 +77,6 @@ func TestGetAccount(t *testing.T) { {accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, {accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.BlacklistedAcct{}}, - // pubID given and matches a host account with Disabled: false and GDPR purpose data to convert - {accountID: "gdpr_deprecated_purpose1", required: false, disabled: false, err: nil}, - {accountID: "gdpr_deprecated_purpose1", required: true, disabled: false, err: nil}, - {accountID: "gdpr_deprecated_purpose1", required: false, disabled: true, err: nil}, - {accountID: "gdpr_deprecated_purpose1", required: true, disabled: true, err: nil}, - // pubID given and matches a host account that has a malformed config {accountID: "malformed_acct", required: false, disabled: false, err: &errortypes.MalformedAcct{}}, {accountID: "malformed_acct", required: true, disabled: false, err: &errortypes.MalformedAcct{}}, @@ -120,7 +102,6 @@ func TestGetAccount(t *testing.T) { assert.NoError(t, cfg.MarshalAccountDefaults()) metrics := &metrics.MetricsEngineMock{} - metrics.Mock.On("RecordAccountGDPRPurposeWarning", mock.Anything, mock.Anything).Return() metrics.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return() account, errors := GetAccount(context.Background(), cfg, fetcher, test.accountID, metrics) @@ -238,160 +219,6 @@ func TestSetDerivedConfig(t *testing.T) { } } -func TestConvertGDPREnforcePurposeFields(t *testing.T) { - enforcePurposeNo := `{"enforce_purpose":"no"}` - enforcePurposeNoMapped := `{"enforce_algo":"full", "enforce_purpose":false}` - enforcePurposeFull := `{"enforce_purpose":"full"}` - enforcePurposeFullMapped := `{"enforce_algo":"full", "enforce_purpose":true}` - - tests := []struct { - description string - giveConfig []byte - wantConfig []byte - wantErr error - wantPurposeFields []string - }{ - { - description: "config is nil", - giveConfig: nil, - wantConfig: nil, - wantErr: nil, - wantPurposeFields: nil, - }, - { - description: "config is empty - no gdpr key", - giveConfig: []byte(``), - wantConfig: []byte(``), - wantErr: nil, - wantPurposeFields: nil, - }, - { - description: "gdpr present but empty", - giveConfig: []byte(`{"gdpr": {}}`), - wantConfig: []byte(`{"gdpr": {}}`), - wantErr: nil, - wantPurposeFields: nil, - }, - { - description: "gdpr present but invalid", - giveConfig: []byte(`{"gdpr": {`), - wantConfig: nil, - wantErr: jsonparser.MalformedJsonError, - wantPurposeFields: nil, - }, - { - description: "gdpr.purpose1 present but empty", - giveConfig: []byte(`{"gdpr":{"purpose1":{}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{}}}`), - wantErr: nil, - wantPurposeFields: nil, - }, - { - description: "gdpr.purpose1.enforce_purpose is set to bool", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":true}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":true}}}`), - wantErr: nil, - wantPurposeFields: nil, - }, - { - description: "gdpr.purpose1.enforce_purpose is set to string full", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":"full"}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":true}}}`), - wantErr: nil, - wantPurposeFields: []string{"purpose1"}, - }, - { - description: "gdpr.purpose1.enforce_purpose is set to string no", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":"no"}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":false}}}`), - wantErr: nil, - wantPurposeFields: []string{"purpose1"}, - }, - { - description: "gdpr.purpose1.enforce_purpose is set to string no and other fields are untouched during conversion", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":"no", "enforce_vendors":true}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":false, "enforce_vendors":true}}}`), - wantErr: nil, - wantPurposeFields: []string{"purpose1"}, - }, - { - description: "gdpr.purpose1.enforce_purpose is set but invalid", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":}}}`), - wantConfig: nil, - wantErr: jsonparser.MalformedJsonError, - wantPurposeFields: nil, - }, - { - description: "gdpr.purpose1.enforce_algo is set", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full"}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full"}}}`), - wantErr: nil, - wantPurposeFields: nil, - }, - { - description: "gdpr.purpose1.enforce_purpose is set to string and enforce_algo is set", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":"full"}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":"full"}}}`), - wantErr: nil, - wantPurposeFields: []string{"purpose1"}, - }, - { - description: "gdpr.purpose1.enforce_purpose is set to string and enforce_algo is set but invalid", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":, "enforce_purpose":"full"}}}`), - wantConfig: nil, - wantErr: jsonparser.MalformedJsonError, - wantPurposeFields: []string{"purpose1"}, - }, - { - description: "gdpr.purpose{1-10}.enforce_purpose are set to strings no and full alternating", - giveConfig: []byte(`{"gdpr":{` + - `"purpose1":` + enforcePurposeNo + - `,"purpose2":` + enforcePurposeFull + - `,"purpose3":` + enforcePurposeNo + - `,"purpose4":` + enforcePurposeFull + - `,"purpose5":` + enforcePurposeNo + - `,"purpose6":` + enforcePurposeFull + - `,"purpose7":` + enforcePurposeNo + - `,"purpose8":` + enforcePurposeFull + - `,"purpose9":` + enforcePurposeNo + - `,"purpose10":` + enforcePurposeFull + - `}}`), - wantConfig: []byte(`{"gdpr":{` + - `"purpose1":` + enforcePurposeNoMapped + - `,"purpose2":` + enforcePurposeFullMapped + - `,"purpose3":` + enforcePurposeNoMapped + - `,"purpose4":` + enforcePurposeFullMapped + - `,"purpose5":` + enforcePurposeNoMapped + - `,"purpose6":` + enforcePurposeFullMapped + - `,"purpose7":` + enforcePurposeNoMapped + - `,"purpose8":` + enforcePurposeFullMapped + - `,"purpose9":` + enforcePurposeNoMapped + - `,"purpose10":` + enforcePurposeFullMapped + - `}}`), - wantErr: nil, - wantPurposeFields: []string{"purpose1", "purpose2", "purpose3", "purpose4", "purpose5", "purpose6", "purpose7", "purpose8", "purpose9", "purpose10"}, - }, - } - - for _, tt := range tests { - metricsMock := &metrics.MetricsEngineMock{} - metricsMock.Mock.On("RecordAccountGDPRPurposeWarning", mock.Anything, mock.Anything).Return() - metricsMock.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return() - - newConfig, err, deprecatedPurposeFields := ConvertGDPREnforcePurposeFields(tt.giveConfig) - if tt.wantErr != nil { - assert.Error(t, err, tt.description) - } - - if len(tt.wantConfig) == 0 { - assert.Equal(t, tt.wantConfig, newConfig, tt.description) - } else { - assert.JSONEq(t, string(tt.wantConfig), string(newConfig), tt.description) - } - assert.Equal(t, tt.wantPurposeFields, deprecatedPurposeFields, tt.description) - } -} - func TestGdprCcpaChannelEnabledMetrics(t *testing.T) { cfg := &config.Configuration{} fetcher := &mockAccountFetcher{} @@ -442,89 +269,6 @@ func TestGdprCcpaChannelEnabledMetrics(t *testing.T) { } } -func TestGdprPurposeWarningMetrics(t *testing.T) { - cfg := &config.Configuration{} - fetcher := &mockAccountFetcher{} - assert.NoError(t, cfg.MarshalAccountDefaults()) - - testCases := []struct { - name string - givenMetric string - givenAccountID string - givenConfig []byte - expectedMetricCount int - }{ - { - name: "Purpose1MetricCalled", - givenAccountID: "gdpr_deprecated_purpose1", - expectedMetricCount: 1, - }, - { - name: "Purpose2MetricCalled", - givenAccountID: "gdpr_deprecated_purpose2", - expectedMetricCount: 1, - }, - { - name: "Purpose3MetricCalled", - givenAccountID: "gdpr_deprecated_purpose3", - expectedMetricCount: 1, - }, - { - name: "Purpose4MetricCalled", - givenAccountID: "gdpr_deprecated_purpose4", - expectedMetricCount: 1, - }, - { - name: "Purpose5MetricCalled", - givenAccountID: "gdpr_deprecated_purpose5", - expectedMetricCount: 1, - }, - { - name: "Purpose6MetricCalled", - givenAccountID: "gdpr_deprecated_purpose6", - expectedMetricCount: 1, - }, - { - name: "Purpose7MetricCalled", - givenAccountID: "gdpr_deprecated_purpose7", - expectedMetricCount: 1, - }, - { - name: "Purpose8MetricCalled", - givenAccountID: "gdpr_deprecated_purpose8", - expectedMetricCount: 1, - }, - { - name: "Purpose9MetricCalled", - givenAccountID: "gdpr_deprecated_purpose9", - expectedMetricCount: 1, - }, - { - name: "Purpose10MetricCalled", - givenAccountID: "gdpr_deprecated_purpose10", - expectedMetricCount: 1, - }, - { - name: "PurposeMetricNotCalled", - givenAccountID: "valid_acct", - expectedMetricCount: 0, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - metrics := &metrics.MetricsEngineMock{} - metrics.Mock.On("RecordAccountGDPRPurposeWarning", mock.Anything, mock.Anything).Return() - metrics.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return() - - _, _ = GetAccount(context.Background(), cfg, fetcher, test.givenAccountID, metrics) - - metrics.AssertNumberOfCalls(t, "RecordAccountGDPRPurposeWarning", test.expectedMetricCount) - }) - - } -} - func TestAccountUpgradeStatusGetAccount(t *testing.T) { cfg := &config.Configuration{} fetcher := &mockAccountFetcher{} @@ -536,24 +280,12 @@ func TestAccountUpgradeStatusGetAccount(t *testing.T) { givenMetrics []string expectedMetricCount int }{ - { - name: "MultipleDeprecatedConfigs", - givenAccountIDs: []string{"gdpr_channel_enabled_deprecated_purpose_acct"}, - givenMetrics: []string{"RecordAccountGDPRChannelEnabledWarning", "RecordAccountGDPRPurposeWarning"}, - expectedMetricCount: 1, - }, { name: "ZeroDeprecatedConfigs", givenAccountIDs: []string{"valid_acct"}, givenMetrics: []string{}, expectedMetricCount: 0, }, - { - name: "OneDeprecatedConfigPurpose", - givenAccountIDs: []string{"gdpr_deprecated_purpose1"}, - givenMetrics: []string{"RecordAccountGDPRPurposeWarning"}, - expectedMetricCount: 1, - }, { name: "OneDeprecatedConfigGDPRChannelEnabled", givenAccountIDs: []string{"gdpr_channel_enabled_acct"}, @@ -568,8 +300,8 @@ func TestAccountUpgradeStatusGetAccount(t *testing.T) { }, { name: "MultipleAccountsWithDeprecatedConfigs", - givenAccountIDs: []string{"gdpr_channel_enabled_acct", "gdpr_deprecated_purpose1"}, - givenMetrics: []string{"RecordAccountGDPRChannelEnabledWarning", "RecordAccountGDPRPurposeWarning"}, + givenAccountIDs: []string{"gdpr_channel_enabled_acct", "ccpa_channel_enabled_acct"}, + givenMetrics: []string{"RecordAccountGDPRChannelEnabledWarning", "RecordAccountCCPAChannelEnabledWarning"}, expectedMetricCount: 2, }, } diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index bf173253e56..757cde336b2 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -322,11 +322,6 @@ func (me *MultiMetricsEngine) RecordBidValidationSecureMarkupWarn(adapter openrt } } -func (me *MultiMetricsEngine) RecordAccountGDPRPurposeWarning(account string, purposeName string) { - for _, thisME := range *me { - thisME.RecordAccountGDPRPurposeWarning(account, purposeName) - } -} func (me *MultiMetricsEngine) RecordAccountGDPRChannelEnabledWarning(account string) { for _, thisME := range *me { thisME.RecordAccountGDPRChannelEnabledWarning(account) @@ -536,9 +531,6 @@ func (me *NilMetricsEngine) RecordBidValidationSecureMarkupError(adapter openrtb func (me *NilMetricsEngine) RecordBidValidationSecureMarkupWarn(adapter openrtb_ext.BidderName, account string) { } -func (me *NilMetricsEngine) RecordAccountGDPRPurposeWarning(account string, purposeName string) { -} - func (me *NilMetricsEngine) RecordAccountGDPRChannelEnabledWarning(account string) { } diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 382dc64c090..37080b05456 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -127,19 +127,9 @@ type accountMetrics struct { bidValidationSecureMarkupWarnMeter metrics.Meter // Account Deprciation Metrics - accountDeprecationWarningsPurpose1Meter metrics.Meter - accountDeprecationWarningsPurpose2Meter metrics.Meter - accountDeprecationWarningsPurpose3Meter metrics.Meter - accountDeprecationWarningsPurpose4Meter metrics.Meter - accountDeprecationWarningsPurpose5Meter metrics.Meter - accountDeprecationWarningsPurpose6Meter metrics.Meter - accountDeprecationWarningsPurpose7Meter metrics.Meter - accountDeprecationWarningsPurpose8Meter metrics.Meter - accountDeprecationWarningsPurpose9Meter metrics.Meter - accountDeprecationWarningsPurpose10Meter metrics.Meter - channelEnabledGDPRMeter metrics.Meter - channelEnabledCCPAMeter metrics.Meter - accountDeprecationSummaryMeter metrics.Meter + channelEnabledGDPRMeter metrics.Meter + channelEnabledCCPAMeter metrics.Meter + accountDeprecationSummaryMeter metrics.Meter } type ModuleMetrics struct { @@ -576,16 +566,6 @@ func (me *Metrics) getAccountMetrics(id string) *accountMetrics { am.bidValidationSecureMarkupMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.response.validation.secure.err", id), me.MetricsRegistry) am.bidValidationSecureMarkupWarnMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.response.validation.secure.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose1Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose1.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose2Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose2.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose3Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose3.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose4Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose4.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose5Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose5.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose6Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose6.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose7Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose7.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose8Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose8.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose9Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose9.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose10Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose10.warn", id), me.MetricsRegistry) am.channelEnabledCCPAMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.ccpa.channel_enabled.warn", id), me.MetricsRegistry) am.channelEnabledGDPRMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.channel_enabled.warn", id), me.MetricsRegistry) am.accountDeprecationSummaryMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.summary", id), me.MetricsRegistry) @@ -635,34 +615,6 @@ func (me *Metrics) RecordDebugRequest(debugEnabled bool, pubID string) { } } -func (me *Metrics) RecordAccountGDPRPurposeWarning(account string, purposeName string) { - if account != PublisherUnknown { - am := me.getAccountMetrics(account) - switch purposeName { - case "purpose1": - am.accountDeprecationWarningsPurpose1Meter.Mark(1) - case "purpose2": - am.accountDeprecationWarningsPurpose2Meter.Mark(1) - case "purpose3": - am.accountDeprecationWarningsPurpose3Meter.Mark(1) - case "purpose4": - am.accountDeprecationWarningsPurpose4Meter.Mark(1) - case "purpose5": - am.accountDeprecationWarningsPurpose5Meter.Mark(1) - case "purpose6": - am.accountDeprecationWarningsPurpose6Meter.Mark(1) - case "purpose7": - am.accountDeprecationWarningsPurpose7Meter.Mark(1) - case "purpose8": - am.accountDeprecationWarningsPurpose8Meter.Mark(1) - case "purpose9": - am.accountDeprecationWarningsPurpose9Meter.Mark(1) - case "purpose10": - am.accountDeprecationWarningsPurpose10Meter.Mark(1) - } - } -} - func (me *Metrics) RecordAccountGDPRChannelEnabledWarning(account string) { if account != PublisherUnknown { am := me.getAccountMetrics(account) diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 515691fe503..3564f9fa2a5 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -1066,94 +1066,6 @@ func TestRecordModuleAccountMetrics(t *testing.T) { } } -func TestRecordAccountGDPRPurposeWarningMetrics(t *testing.T) { - testCases := []struct { - name string - givenPurposeName string - expectedP1MetricCount int64 - expectedP2MetricCount int64 - expectedP3MetricCount int64 - expectedP4MetricCount int64 - expectedP5MetricCount int64 - expectedP6MetricCount int64 - expectedP7MetricCount int64 - expectedP8MetricCount int64 - expectedP9MetricCount int64 - expectedP10MetricCount int64 - }{ - { - name: "Purpose1MetricIncremented", - givenPurposeName: "purpose1", - expectedP1MetricCount: 1, - }, - { - name: "Purpose2MetricIncremented", - givenPurposeName: "purpose2", - expectedP2MetricCount: 1, - }, - { - name: "Purpose3MetricIncremented", - givenPurposeName: "purpose3", - expectedP3MetricCount: 1, - }, - { - name: "Purpose4MetricIncremented", - givenPurposeName: "purpose4", - expectedP4MetricCount: 1, - }, - { - name: "Purpose5MetricIncremented", - givenPurposeName: "purpose5", - expectedP5MetricCount: 1, - }, - { - name: "Purpose6MetricIncremented", - givenPurposeName: "purpose6", - expectedP6MetricCount: 1, - }, - { - name: "Purpose7MetricIncremented", - givenPurposeName: "purpose7", - expectedP7MetricCount: 1, - }, - { - name: "Purpose8MetricIncremented", - givenPurposeName: "purpose8", - expectedP8MetricCount: 1, - }, - { - name: "Purpose9MetricIncremented", - givenPurposeName: "purpose9", - expectedP9MetricCount: 1, - }, - { - name: "Purpose10MetricIncremented", - givenPurposeName: "purpose10", - expectedP10MetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) - - m.RecordAccountGDPRPurposeWarning("acct-id", test.givenPurposeName) - am := m.getAccountMetrics("acct-id") - - assert.Equal(t, test.expectedP1MetricCount, am.accountDeprecationWarningsPurpose1Meter.Count()) - assert.Equal(t, test.expectedP2MetricCount, am.accountDeprecationWarningsPurpose2Meter.Count()) - assert.Equal(t, test.expectedP3MetricCount, am.accountDeprecationWarningsPurpose3Meter.Count()) - assert.Equal(t, test.expectedP4MetricCount, am.accountDeprecationWarningsPurpose4Meter.Count()) - assert.Equal(t, test.expectedP5MetricCount, am.accountDeprecationWarningsPurpose5Meter.Count()) - assert.Equal(t, test.expectedP6MetricCount, am.accountDeprecationWarningsPurpose6Meter.Count()) - assert.Equal(t, test.expectedP7MetricCount, am.accountDeprecationWarningsPurpose7Meter.Count()) - assert.Equal(t, test.expectedP8MetricCount, am.accountDeprecationWarningsPurpose8Meter.Count()) - assert.Equal(t, test.expectedP9MetricCount, am.accountDeprecationWarningsPurpose9Meter.Count()) - assert.Equal(t, test.expectedP10MetricCount, am.accountDeprecationWarningsPurpose10Meter.Count()) - }) - } -} - func TestRecordAccountGDPRChannelEnabledWarningMetrics(t *testing.T) { testCases := []struct { name string diff --git a/metrics/metrics.go b/metrics/metrics.go index a73019bed5e..7315fa72712 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -470,7 +470,6 @@ type MetricsEngine interface { RecordModuleSuccessRejected(labels ModuleLabels) RecordModuleExecutionError(labels ModuleLabels) RecordModuleTimeout(labels ModuleLabels) - RecordAccountGDPRPurposeWarning(account string, purposeName string) RecordAccountGDPRChannelEnabledWarning(account string) RecordAccountCCPAChannelEnabledWarning(account string) RecordAccountUpgradeStatus(account string) diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index a518b98d404..f6ecf57e37f 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -222,10 +222,6 @@ func (me *MetricsEngineMock) RecordModuleTimeout(labels ModuleLabels) { me.Called(labels) } -func (me *MetricsEngineMock) RecordAccountGDPRPurposeWarning(account string, purposeName string) { - me.Called(account, purposeName) -} - func (me *MetricsEngineMock) RecordAccountGDPRChannelEnabledWarning(account string) { me.Called(account) } diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index c57ba2214f6..363e5527fbc 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -89,19 +89,9 @@ type Metrics struct { accountBidResponseSecureMarkupWarn *prometheus.CounterVec // Account Deprecation Metrics - accountDeprecationWarningsPurpose1 prometheus.Counter - accountDeprecationWarningsPurpose2 prometheus.Counter - accountDeprecationWarningsPurpose3 prometheus.Counter - accountDeprecationWarningsPurpose4 prometheus.Counter - accountDeprecationWarningsPurpose5 prometheus.Counter - accountDeprecationWarningsPurpose6 prometheus.Counter - accountDeprecationWarningsPurpose7 prometheus.Counter - accountDeprecationWarningsPurpose8 prometheus.Counter - accountDeprecationWarningsPurpose9 prometheus.Counter - accountDeprecationWarningsPurpose10 prometheus.Counter - channelEnabledGDPR prometheus.Counter - channelEnabledCCPA prometheus.Counter - accountDeprecationSummary prometheus.Counter + channelEnabledGDPR prometheus.Counter + channelEnabledCCPA prometheus.Counter + accountDeprecationSummary prometheus.Counter // Module Metrics as a map where the key is the module name moduleDuration map[string]*prometheus.HistogramVec @@ -512,37 +502,6 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "Count of AdsCert request, and if they were successfully sent.", []string{successLabel}) - metrics.accountDeprecationWarningsPurpose1 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose1_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose1 field") - metrics.accountDeprecationWarningsPurpose2 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose2_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose2 field") - metrics.accountDeprecationWarningsPurpose3 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose3_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose3 field") - metrics.accountDeprecationWarningsPurpose4 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose4_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose4 field") - metrics.accountDeprecationWarningsPurpose5 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose5_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose5 field") - metrics.accountDeprecationWarningsPurpose6 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose6_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose6 field") - metrics.accountDeprecationWarningsPurpose7 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose7_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose7 field") - metrics.accountDeprecationWarningsPurpose8 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose8_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose8 field") - metrics.accountDeprecationWarningsPurpose9 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose9_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose9 field") - metrics.accountDeprecationWarningsPurpose10 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose10_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose10 field") - metrics.channelEnabledCCPA = newCounterWithoutLabels(cfg, reg, "account_config_ccpa_channel_enabled_warn", "Count of requests referencing an account whose config specifies a depreceated ccpa.channel_enabled field") @@ -734,33 +693,6 @@ func (m *Metrics) RecordDebugRequest(debugEnabled bool, pubID string) { } } -func (m *Metrics) RecordAccountGDPRPurposeWarning(account string, purposeName string) { - if account != metrics.PublisherUnknown { - switch purposeName { - case "purpose1": - m.accountDeprecationWarningsPurpose1.Inc() - case "purpose2": - m.accountDeprecationWarningsPurpose2.Inc() - case "purpose3": - m.accountDeprecationWarningsPurpose3.Inc() - case "purpose4": - m.accountDeprecationWarningsPurpose4.Inc() - case "purpose5": - m.accountDeprecationWarningsPurpose5.Inc() - case "purpose6": - m.accountDeprecationWarningsPurpose6.Inc() - case "purpose7": - m.accountDeprecationWarningsPurpose7.Inc() - case "purpose8": - m.accountDeprecationWarningsPurpose8.Inc() - case "purpose9": - m.accountDeprecationWarningsPurpose9.Inc() - case "purpose10": - m.accountDeprecationWarningsPurpose10.Inc() - } - } -} - func (m *Metrics) RecordAccountGDPRChannelEnabledWarning(account string) { if account != metrics.PublisherUnknown { m.channelEnabledGDPR.Inc() diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 32f7848ccff..90d331fe5ae 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -1977,91 +1977,6 @@ func TestRecordModuleMetrics(t *testing.T) { } } -func TestRecordAccountGDPRPurposeWarningMetrics(t *testing.T) { - testCases := []struct { - name string - givenPurposeName string - expectedP1MetricCount float64 - expectedP2MetricCount float64 - expectedP3MetricCount float64 - expectedP4MetricCount float64 - expectedP5MetricCount float64 - expectedP6MetricCount float64 - expectedP7MetricCount float64 - expectedP8MetricCount float64 - expectedP9MetricCount float64 - expectedP10MetricCount float64 - }{ - { - name: "Purpose1MetricIncremented", - givenPurposeName: "purpose1", - expectedP1MetricCount: 1, - }, - { - name: "Purpose2MetricIncremented", - givenPurposeName: "purpose2", - expectedP2MetricCount: 1, - }, - { - name: "Purpose3MetricIncremented", - givenPurposeName: "purpose3", - expectedP3MetricCount: 1, - }, - { - name: "Purpose4MetricIncremented", - givenPurposeName: "purpose4", - expectedP4MetricCount: 1, - }, - { - name: "Purpose5MetricIncremented", - givenPurposeName: "purpose5", - expectedP5MetricCount: 1, - }, - { - name: "Purpose6MetricIncremented", - givenPurposeName: "purpose6", - expectedP6MetricCount: 1, - }, - { - name: "Purpose7MetricIncremented", - givenPurposeName: "purpose7", - expectedP7MetricCount: 1, - }, - { - name: "Purpose8MetricIncremented", - givenPurposeName: "purpose8", - expectedP8MetricCount: 1, - }, - { - name: "Purpose9MetricIncremented", - givenPurposeName: "purpose9", - expectedP9MetricCount: 1, - }, - { - name: "Purpose10MetricIncremented", - givenPurposeName: "purpose10", - expectedP10MetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - m := createMetricsForTesting() - m.RecordAccountGDPRPurposeWarning("acct-id", test.givenPurposeName) - - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose1, test.expectedP1MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose2, test.expectedP2MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose3, test.expectedP3MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose4, test.expectedP4MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose5, test.expectedP5MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose6, test.expectedP6MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose7, test.expectedP7MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose8, test.expectedP8MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose9, test.expectedP9MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose10, test.expectedP10MetricCount) - }) - } -} - func TestRecordAccountGDPRChannelEnabledWarningMetrics(t *testing.T) { testCases := []struct { name string From 13607862cf00ca1a721067772315368eada972ce Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 2 Oct 2023 16:38:57 -0400 Subject: [PATCH 031/138] Remove Deprecated Config: Account Blacklist (#3156) --- account/account.go | 11 +-- account/account_test.go | 22 ++--- config/config.go | 10 -- endpoints/cookie_sync.go | 2 +- endpoints/cookie_sync_test.go | 4 +- endpoints/events/account_test.go | 78 ++++++---------- endpoints/events/event.go | 2 +- endpoints/events/event_test.go | 12 +-- endpoints/openrtb2/amp_auction.go | 4 +- endpoints/openrtb2/amp_auction_test.go | 15 +-- endpoints/openrtb2/auction.go | 4 +- endpoints/openrtb2/auction_benchmark_test.go | 10 +- endpoints/openrtb2/auction_test.go | 4 +- .../required-blacklisted-acct.json | 91 ------------------- .../with-account/required-disabled-acct.json} | 8 +- .../blacklisted-site-publisher.json | 90 ------------------ endpoints/openrtb2/test_utils.go | 19 +--- endpoints/openrtb2/video_auction.go | 21 ++--- endpoints/openrtb2/video_auction_test.go | 40 +------- endpoints/setuid_test.go | 24 ----- errortypes/code.go | 2 +- errortypes/errortypes.go | 15 ++- 22 files changed, 81 insertions(+), 407 deletions(-) delete mode 100644 endpoints/openrtb2/sample-requests/account-required/with-account/required-blacklisted-acct.json rename endpoints/openrtb2/sample-requests/{blacklisted/blacklisted-app-publisher.json => account-required/with-account/required-disabled-acct.json} (86%) delete mode 100644 endpoints/openrtb2/sample-requests/blacklisted/blacklisted-site-publisher.json diff --git a/account/account.go b/account/account.go index 7b5f9435a11..70527d0f6ee 100644 --- a/account/account.go +++ b/account/account.go @@ -4,7 +4,9 @@ import ( "context" "encoding/json" "fmt" + "github.com/prebid/go-gdpr/consentconstants" + "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/metrics" @@ -15,17 +17,12 @@ import ( // GetAccount looks up the config.Account object referenced by the given accountID, with access rules applied func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_requests.AccountFetcher, accountID string, me metrics.MetricsEngine) (account *config.Account, errs []error) { - // Check BlacklistedAcctMap until we have deprecated it - if _, found := cfg.BlacklistedAcctMap[accountID]; found { - return nil, []error{&errortypes.BlacklistedAcct{ - Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", accountID), - }} - } if cfg.AccountRequired && accountID == metrics.PublisherUnknown { return nil, []error{&errortypes.AcctRequired{ Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."), }} } + if accountJSON, accErrs := fetcher.FetchAccount(ctx, cfg.AccountDefaultsJSON(), accountID); len(accErrs) > 0 || accountJSON == nil { // accountID does not reference a valid account for _, e := range accErrs { @@ -79,7 +76,7 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r setDerivedConfig(account) } if account.Disabled { - errs = append(errs, &errortypes.BlacklistedAcct{ + errs = append(errs, &errortypes.AccountDisabled{ Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", accountID), }) return nil, errs diff --git a/account/account_test.go b/account/account_test.go index d5223f76119..da0457f8d47 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -48,19 +48,16 @@ func TestGetAccount(t *testing.T) { // expected error, or nil if account should be found err error }{ - // Blacklisted account is always rejected even in permissive setup - {accountID: "bad_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}}, - // empty pubID {accountID: unknown, required: false, disabled: false, err: nil}, {accountID: unknown, required: true, disabled: false, err: &errortypes.AcctRequired{}}, - {accountID: unknown, required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: unknown, required: false, disabled: true, err: &errortypes.AccountDisabled{}}, {accountID: unknown, required: true, disabled: true, err: &errortypes.AcctRequired{}}, // pubID given but is not a valid host account (does not exist) {accountID: "doesnt_exist_acct", required: false, disabled: false, err: nil}, {accountID: "doesnt_exist_acct", required: true, disabled: false, err: nil}, - {accountID: "doesnt_exist_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: "doesnt_exist_acct", required: false, disabled: true, err: &errortypes.AccountDisabled{}}, {accountID: "doesnt_exist_acct", required: true, disabled: true, err: &errortypes.AcctRequired{}}, // pubID given and matches a valid host account with Disabled: false @@ -72,10 +69,10 @@ func TestGetAccount(t *testing.T) { {accountID: "invalid_acct_ipv6_ipv4", required: true, disabled: false, err: nil, checkDefaultIP: true}, // pubID given and matches a host account explicitly disabled (Disabled: true on account json) - {accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}}, - {accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.BlacklistedAcct{}}, - {accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, - {accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.AccountDisabled{}}, + {accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.AccountDisabled{}}, + {accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.AccountDisabled{}}, + {accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.AccountDisabled{}}, // pubID given and matches a host account that has a malformed config {accountID: "malformed_acct", required: false, disabled: false, err: &errortypes.MalformedAcct{}}, @@ -86,7 +83,7 @@ func TestGetAccount(t *testing.T) { // account not provided (does not exist) {accountID: "", required: false, disabled: false, err: nil}, {accountID: "", required: true, disabled: false, err: nil}, - {accountID: "", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: "", required: false, disabled: true, err: &errortypes.AccountDisabled{}}, {accountID: "", required: true, disabled: true, err: &errortypes.AcctRequired{}}, } @@ -94,9 +91,8 @@ func TestGetAccount(t *testing.T) { description := fmt.Sprintf(`ID=%s/required=%t/disabled=%t`, test.accountID, test.required, test.disabled) t.Run(description, func(t *testing.T) { cfg := &config.Configuration{ - BlacklistedAcctMap: map[string]bool{"bad_acct": true}, - AccountRequired: test.required, - AccountDefaults: config.Account{Disabled: test.disabled}, + AccountRequired: test.required, + AccountDefaults: config.Account{Disabled: test.disabled}, } fetcher := &mockAccountFetcher{} assert.NoError(t, cfg.MarshalAccountDefaults()) diff --git a/config/config.go b/config/config.go index a8d276a15c2..cf3591dd564 100644 --- a/config/config.go +++ b/config/config.go @@ -67,9 +67,6 @@ type Configuration struct { // Array of blacklisted apps that is used to create the hash table BlacklistedAppMap so App.ID's can be instantly accessed. BlacklistedApps []string `mapstructure:"blacklisted_apps,flow"` BlacklistedAppMap map[string]bool - // Array of blacklisted accounts that is used to create the hash table BlacklistedAcctMap so Account.ID's can be instantly accessed. - BlacklistedAccts []string `mapstructure:"blacklisted_accts,flow"` - BlacklistedAcctMap map[string]bool // Is publisher/account ID required to be submitted in the OpenRTB2 request AccountRequired bool `mapstructure:"account_required"` // AccountDefaults defines default settings for valid accounts that are partially defined @@ -757,13 +754,6 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin c.BlacklistedAppMap[c.BlacklistedApps[i]] = true } - // To look for a request's account id in O(1) time, we fill this hash table located in the - // the BlacklistedAccts field of the Configuration struct defined in this file - c.BlacklistedAcctMap = make(map[string]bool) - for i := 0; i < len(c.BlacklistedAccts); i++ { - c.BlacklistedAcctMap[c.BlacklistedAccts[i]] = true - } - // Migrate combo stored request config to separate stored_reqs and amp stored_reqs configs. resolvedStoredRequestsConfig(&c) diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index 250d5fd07d3..bbee3847014 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -367,7 +367,7 @@ func combineErrors(errs []error) error { for _, err := range errs { // preserve knowledge of special account errors switch errortypes.ReadCode(err) { - case errortypes.BlacklistedAcctErrorCode: + case errortypes.AccountDisabledErrorCode: return errCookieSyncAccountBlocked case errortypes.AcctRequiredErrorCode: return errCookieSyncAccountInvalid diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 29fe74b273c..80de2b5ff50 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -1938,7 +1938,7 @@ func TestCombineErrors(t *testing.T) { }, { description: "Special Case: blocked (rejected via block list)", - givenErrorList: []error{&errortypes.BlacklistedAcct{}}, + givenErrorList: []error{&errortypes.AccountDisabled{}}, expectedError: errCookieSyncAccountBlocked, }, { @@ -1953,7 +1953,7 @@ func TestCombineErrors(t *testing.T) { }, { description: "Special Case: multiple special cases, first one wins", - givenErrorList: []error{&errortypes.BlacklistedAcct{}, &errortypes.AcctRequired{}, &errortypes.MalformedAcct{}}, + givenErrorList: []error{&errortypes.AccountDisabled{}, &errortypes.AcctRequired{}, &errortypes.MalformedAcct{}}, expectedError: errCookieSyncAccountBlocked, }, } diff --git a/endpoints/events/account_test.go b/endpoints/events/account_test.go index 7efb8af782b..8477b43b49b 100644 --- a/endpoints/events/account_test.go +++ b/endpoints/events/account_test.go @@ -2,7 +2,6 @@ package events import ( "errors" - "fmt" "io" "net/http" "net/http/httptest" @@ -15,19 +14,18 @@ import ( "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/stored_requests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestHandleAccountServiceErrors(t *testing.T) { tests := map[string]struct { - fetcher *mockAccountsFetcher - cfg *config.Configuration - accountID string - want struct { - code int - response string - } + fetcher *mockAccountsFetcher + cfg *config.Configuration + accountID string + wantCode int + wantResponse string }{ - "badRequest": { + "bad-request": { fetcher: &mockAccountsFetcher{ Fail: true, Error: errors.New("some error"), @@ -40,16 +38,11 @@ func TestHandleAccountServiceErrors(t *testing.T) { TimeoutMS: int64(2000), AllowUnknownBidder: false, }, }, - accountID: "testacc", - want: struct { - code int - response string - }{ - code: 400, - response: "Invalid request: some error\nInvalid request: Prebid-server could not verify the Account ID. Please reach out to the prebid server host.\n", - }, + accountID: "testacc", + wantCode: 400, + wantResponse: "Invalid request: some error\nInvalid request: Prebid-server could not verify the Account ID. Please reach out to the prebid server host.\n", }, - "malformedAccountConfig": { + "malformed-account-config": { fetcher: &mockAccountsFetcher{ Fail: true, Error: &errortypes.MalformedAcct{}, @@ -60,34 +53,25 @@ func TestHandleAccountServiceErrors(t *testing.T) { TimeoutMS: int64(2000), AllowUnknownBidder: false, }, }, - accountID: "malformed_acct", - want: struct { - code int - response string - }{ - code: 500, - response: "Invalid request: The prebid-server account config for account id \"malformed_acct\" is malformed. Please reach out to the prebid server host.\n", - }, + accountID: "malformed_acct", + wantCode: 500, + wantResponse: "Invalid request: The prebid-server account config for account id \"malformed_acct\" is malformed. Please reach out to the prebid server host.\n", }, - "serviceUnavailable": { + "service-unavailable": { fetcher: &mockAccountsFetcher{ Fail: false, }, cfg: &config.Configuration{ - BlacklistedAcctMap: map[string]bool{"testacc": true}, - MaxRequestSize: maxSize, + AccountDefaults: config.Account{}, + AccountRequired: true, + MaxRequestSize: maxSize, VTrack: config.VTrack{ TimeoutMS: int64(2000), AllowUnknownBidder: false, }, }, - accountID: "testacc", - want: struct { - code int - response string - }{ - code: 503, - response: "Invalid request: Prebid-server has disabled Account ID: testacc, please reach out to the prebid server host.\n", - }, + accountID: "disabled_acct", + wantCode: 503, + wantResponse: "Invalid request: Prebid-server has disabled Account ID: disabled_acct, please reach out to the prebid server host.\n", }, "timeout": { fetcher: &mockAccountsFetcher{ @@ -106,19 +90,13 @@ func TestHandleAccountServiceErrors(t *testing.T) { AllowUnknownBidder: false, }, }, - accountID: "testacc", - want: struct { - code int - response string - }{ - code: 504, - response: "Invalid request: context deadline exceeded\nInvalid request: Prebid-server could not verify the Account ID. Please reach out to the prebid server host.\n", - }, + accountID: "testacc", + wantCode: 504, + wantResponse: "Invalid request: context deadline exceeded\nInvalid request: Prebid-server could not verify the Account ID. Please reach out to the prebid server host.\n", }, } for name, test := range tests { - handlers := []struct { name string h httprouter.Handle @@ -137,13 +115,11 @@ func TestHandleAccountServiceErrors(t *testing.T) { // execute handler.h(recorder, handler.r, nil) d, err := io.ReadAll(recorder.Result().Body) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // validate - assert.Equal(t, test.want.code, recorder.Result().StatusCode, fmt.Sprintf("Expected %d", test.want.code)) - assert.Equal(t, test.want.response, string(d)) + assert.Equal(t, test.wantCode, recorder.Result().StatusCode) + assert.Equal(t, test.wantResponse, string(d)) }) } } diff --git a/endpoints/events/event.go b/endpoints/events/event.go index e8084f1b273..7f923eda09d 100644 --- a/endpoints/events/event.go +++ b/endpoints/events/event.go @@ -206,7 +206,7 @@ func HandleAccountServiceErrors(errs []error) (status int, messages []string) { errCode := errortypes.ReadCode(er) - if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode { + if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.AccountDisabledErrorCode { status = http.StatusServiceUnavailable } if errCode == errortypes.MalformedAcctErrorCode { diff --git a/endpoints/events/event_test.go b/endpoints/events/event_test.go index 95711ba1328..7f129634221 100644 --- a/endpoints/events/event_test.go +++ b/endpoints/events/event_test.go @@ -20,7 +20,6 @@ import ( "github.com/stretchr/testify/assert" ) -// Mock Analytics Module type eventsMockAnalyticsModule struct { Fail bool Error error @@ -31,35 +30,30 @@ func (e *eventsMockAnalyticsModule) LogAuctionObject(ao *analytics.AuctionObject if e.Fail { panic(e.Error) } - return } func (e *eventsMockAnalyticsModule) LogVideoObject(vo *analytics.VideoObject) { if e.Fail { panic(e.Error) } - return } func (e *eventsMockAnalyticsModule) LogCookieSyncObject(cso *analytics.CookieSyncObject) { if e.Fail { panic(e.Error) } - return } func (e *eventsMockAnalyticsModule) LogSetUIDObject(so *analytics.SetUIDObject) { if e.Fail { panic(e.Error) } - return } func (e *eventsMockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject) { if e.Fail { panic(e.Error) } - return } func (e *eventsMockAnalyticsModule) LogNotificationEventObject(ne *analytics.NotificationEvent) { @@ -67,15 +61,13 @@ func (e *eventsMockAnalyticsModule) LogNotificationEventObject(ne *analytics.Not panic(e.Error) } e.Invoked = true - - return } -// Mock Account fetcher var mockAccountData = map[string]json.RawMessage{ "events_enabled": json.RawMessage(`{"events": {"enabled":true}}`), "events_disabled": json.RawMessage(`{"events": {"enabled":false}}`), "malformed_acct": json.RawMessage(`{"events": {"enabled":"invalid type"}}`), + "disabled_acct": json.RawMessage(`{"disabled": true}`), } type mockAccountsFetcher struct { @@ -102,7 +94,7 @@ func (maf mockAccountsFetcher) FetchAccount(ctx context.Context, defaultAccountJ return nil, []error{maf.Error} } - return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} + return nil, []error{stored_requests.NotFoundError{ID: accountID, DataType: "Account"}} } // Tests diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 2879610d8e4..aa80d0756c3 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -77,7 +77,7 @@ func NewAmpEndpoint( return nil, errors.New("NewAmpEndpoint requires non-nil arguments.") } - defRequest := defReqJSON != nil && len(defReqJSON) > 0 + defRequest := len(defReqJSON) > 0 ipValidator := iputil.PublicNetworkIPValidator{ IPv4PrivateNetworks: cfg.RequestValidation.IPv4PrivateNetworksParsed, @@ -208,7 +208,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h metricsStatus := metrics.RequestStatusBadInput for _, er := range errL { errCode := errortypes.ReadCode(er) - if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode { + if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.AccountDisabledErrorCode { httpStatus = http.StatusServiceUnavailable metricsStatus = metrics.RequestStatusBlacklisted break diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 87033f71b64..89d9c472925 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -98,8 +98,6 @@ func TestGoodAmpRequests(t *testing.T) { if test.Config != nil { cfg.BlacklistedApps = test.Config.BlacklistedApps cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap() - cfg.BlacklistedAccts = test.Config.BlacklistedAccounts - cfg.BlacklistedAcctMap = test.Config.getBlackListedAccountMap() cfg.AccountRequired = test.Config.AccountRequired } @@ -121,7 +119,7 @@ func TestGoodAmpRequests(t *testing.T) { // Assertions if assert.Equal(t, test.ExpectedReturnCode, recorder.Code, "Expected status %d. Got %d. Amp test file: %s", http.StatusOK, recorder.Code, filename) { if test.ExpectedReturnCode == http.StatusOK { - assert.JSONEq(t, string(test.ExpectedAmpResponse), string(recorder.Body.Bytes()), "Not the expected response. Test file: %s", filename) + assert.JSONEq(t, string(test.ExpectedAmpResponse), recorder.Body.String(), "Not the expected response. Test file: %s", filename) } else { assert.Equal(t, test.ExpectedErrorMessage, recorder.Body.String(), filename) } @@ -148,11 +146,6 @@ func TestAccountErrors(t *testing.T) { storedReqID: "1", filename: "account-malformed/malformed-acct.json", }, - { - description: "Blocked account", - storedReqID: "1", - filename: "blacklisted/blacklisted-site-publisher.json", - }, } for _, tt := range tests { @@ -169,9 +162,7 @@ func TestAccountErrors(t *testing.T) { test.endpointType = AMP_ENDPOINT cfg := &config.Configuration{ - BlacklistedAccts: []string{"bad_acct"}, - BlacklistedAcctMap: map[string]bool{"bad_acct": true}, - MaxRequestSize: maxSize, + MaxRequestSize: maxSize, } cfg.MarshalAccountDefaults() @@ -2328,7 +2319,7 @@ func TestSendAmpResponse_LogsErrors(t *testing.T) { account := &config.Account{DebugAllow: true} reqWrapper := openrtb_ext.RequestWrapper{BidRequest: test.request} - labels, ao = sendAmpResponse(test.writer, test.hookExecutor, &exchange.AuctionResponse{BidResponse: test.response}, &reqWrapper, account, labels, ao, nil) + _, ao = sendAmpResponse(test.writer, test.hookExecutor, &exchange.AuctionResponse{BidResponse: test.response}, &reqWrapper, account, labels, ao, nil) assert.Equal(t, ao.Errors, test.expectedErrors, "Invalid errors.") assert.Equal(t, test.expectedStatus, ao.Status, "Invalid HTTP response status.") diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 810741982ef..b625c0ff54c 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -101,7 +101,7 @@ func NewEndpoint( return nil, errors.New("NewEndpoint requires non-nil arguments.") } - defRequest := defReqJSON != nil && len(defReqJSON) > 0 + defRequest := len(defReqJSON) > 0 ipValidator := iputil.PublicNetworkIPValidator{ IPv4PrivateNetworks: cfg.RequestValidation.IPv4PrivateNetworksParsed, @@ -2349,7 +2349,7 @@ func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) boo metricsStatus := metrics.RequestStatusBadInput for _, err := range errs { erVal := errortypes.ReadCode(err) - if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.BlacklistedAcctErrorCode { + if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.AccountDisabledErrorCode { httpStatus = http.StatusServiceUnavailable metricsStatus = metrics.RequestStatusBlacklisted break diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index a3c19ee0b10..ee74548ea47 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -147,12 +147,10 @@ func BenchmarkValidWholeExemplary(b *testing.B) { test.endpointType = OPENRTB_ENDPOINT cfg := &config.Configuration{ - MaxRequestSize: maxSize, - BlacklistedApps: test.Config.BlacklistedApps, - BlacklistedAppMap: test.Config.getBlacklistedAppMap(), - BlacklistedAccts: test.Config.BlacklistedAccounts, - BlacklistedAcctMap: test.Config.getBlackListedAccountMap(), - AccountRequired: test.Config.AccountRequired, + MaxRequestSize: maxSize, + BlacklistedApps: test.Config.BlacklistedApps, + BlacklistedAppMap: test.Config.getBlacklistedAppMap(), + AccountRequired: test.Config.AccountRequired, } auctionEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index c071d6b0950..b5631c93d3d 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -160,8 +160,6 @@ func runJsonBasedTest(t *testing.T, filename, desc string) { if test.Config != nil { cfg.BlacklistedApps = test.Config.BlacklistedApps cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap() - cfg.BlacklistedAccts = test.Config.BlacklistedAccounts - cfg.BlacklistedAcctMap = test.Config.getBlackListedAccountMap() cfg.AccountRequired = test.Config.AccountRequired } cfg.MarshalAccountDefaults() @@ -5803,7 +5801,7 @@ func TestSendAuctionResponse_LogsErrors(t *testing.T) { ao := analytics.AuctionObject{} account := &config.Account{DebugAllow: true} - labels, ao = sendAuctionResponse(writer, test.hookExecutor, test.response, test.request, account, labels, ao) + _, ao = sendAuctionResponse(writer, test.hookExecutor, test.response, test.request, account, labels, ao) assert.Equal(t, ao.Errors, test.expectedErrors, "Invalid errors.") assert.Equal(t, test.expectedStatus, ao.Status, "Invalid HTTP response status.") diff --git a/endpoints/openrtb2/sample-requests/account-required/with-account/required-blacklisted-acct.json b/endpoints/openrtb2/sample-requests/account-required/with-account/required-blacklisted-acct.json deleted file mode 100644 index 894a92fdb27..00000000000 --- a/endpoints/openrtb2/sample-requests/account-required/with-account/required-blacklisted-acct.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "description": "Account is required but request comes with a blacklisted account id", - "config": { - "accountRequired": true, - "blacklistedAccts": ["bad_acct"] - }, - "mockBidRequest": { - "id": "some-request-id", - "user": { - "ext": { - "consent": "gdpr-consent-string", - "prebid": { - "buyeruids": { - "appnexus": "override-appnexus-id-in-cookie" - } - } - } - }, - "app": { - "id": "cool_app", - "publisher": { - "id": "bad_acct" - } - }, - "regs": { - "ext": { - "gdpr": 1 - } - }, - "imp": [ - { - "id": "some-impression-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - }, - "districtm": { - "placementId": 105 - }, - "rubicon": { - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510 - } - } - } - ], - "tmax": 500, - "ext": { - "prebid": { - "aliases": { - "districtm": "appnexus" - }, - "bidadjustmentfactors": { - "appnexus": 1.01, - "districtm": 0.98, - "rubicon": 0.99 - }, - "cache": { - "bids": {} - }, - "targeting": { - "includewinners": false, - "pricegranularity": { - "precision": 2, - "ranges": [ - { - "max": 20, - "increment": 0.10 - } - ] - } - } - } - } - }, - "expectedReturnCode": 503, - "expectedErrorMessage": "Invalid request: Prebid-server has disabled Account ID: bad_acct, please reach out to the prebid server host.\n" -} diff --git a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app-publisher.json b/endpoints/openrtb2/sample-requests/account-required/with-account/required-disabled-acct.json similarity index 86% rename from endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app-publisher.json rename to endpoints/openrtb2/sample-requests/account-required/with-account/required-disabled-acct.json index ef7a93b8bad..31a0021b7c1 100644 --- a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app-publisher.json +++ b/endpoints/openrtb2/sample-requests/account-required/with-account/required-disabled-acct.json @@ -1,7 +1,7 @@ { - "description": "This is a perfectly valid request except that it comes with a blacklisted app publisher account", + "description": "Account is required but request comes with a disabled account id", "config": { - "blacklistedAccts": ["bad_acct"] + "accountRequired": true }, "mockBidRequest": { "id": "some-request-id", @@ -18,7 +18,7 @@ "app": { "id": "cool_app", "publisher": { - "id": "bad_acct" + "id": "disabled_acct" } }, "regs": { @@ -86,5 +86,5 @@ } }, "expectedReturnCode": 503, - "expectedErrorMessage": "Invalid request: Prebid-server has disabled Account ID: bad_acct, please reach out to the prebid server host.\n" + "expectedErrorMessage": "Invalid request: Prebid-server has disabled Account ID: disabled_acct, please reach out to the prebid server host.\n" } diff --git a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-site-publisher.json b/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-site-publisher.json deleted file mode 100644 index cdec20d22ba..00000000000 --- a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-site-publisher.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "description": "This is a perfectly valid request except that it comes with a blacklisted site publisher account", - "config": { - "blacklistedAccts": ["bad_acct"] - }, - "mockBidRequest": { - "id": "some-request-id", - "user": { - "ext": { - "consent": "gdpr-consent-string", - "prebid": { - "buyeruids": { - "appnexus": "override-appnexus-id-in-cookie" - } - } - } - }, - "site": { - "id": "cool_site", - "publisher": { - "id": "bad_acct" - } - }, - "regs": { - "ext": { - "gdpr": 1 - } - }, - "imp": [ - { - "id": "some-impression-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - }, - "districtm": { - "placementId": 105 - }, - "rubicon": { - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510 - } - } - } - ], - "tmax": 500, - "ext": { - "prebid": { - "aliases": { - "districtm": "appnexus" - }, - "bidadjustmentfactors": { - "appnexus": 1.01, - "districtm": 0.98, - "rubicon": 0.99 - }, - "cache": { - "bids": {} - }, - "targeting": { - "includewinners": false, - "pricegranularity": { - "precision": 2, - "ranges": [ - { - "max": 20, - "increment": 0.10 - } - ] - } - } - } - } - }, - "expectedReturnCode": 503, - "expectedErrorMessage": "Invalid request: Prebid-server has disabled Account ID: bad_acct, please reach out to the prebid server host.\n" -} diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index 3d249dc8d59..2d22f1e4ebe 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -84,7 +84,6 @@ type testCase struct { type testConfigValues struct { AccountRequired bool `json:"accountRequired"` AliasJSON string `json:"aliases"` - BlacklistedAccounts []string `json:"blacklistedAccts"` BlacklistedApps []string `json:"blacklistedApps"` DisabledAdapters []string `json:"disabledAdapters"` CurrencyRates map[string]map[string]float64 `json:"currencyRates"` @@ -932,7 +931,6 @@ func (s mockCurrencyRatesClient) handle(w http.ResponseWriter, req *http.Request return } w.Write(currencyServerJsonResponse) - return } // mockBidderHandler carries mock bidder server information that will be read from JSON test files @@ -994,7 +992,6 @@ func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { return } w.Write(serverJsonResponse) - return } // mockAdapter is a mock impression-splitting adapter @@ -1154,18 +1151,6 @@ func (tc *testConfigValues) getBlacklistedAppMap() map[string]bool { return blacklistedAppMap } -func (tc *testConfigValues) getBlackListedAccountMap() map[string]bool { - var blacklistedAccountMap map[string]bool - - if len(tc.BlacklistedAccounts) > 0 { - blacklistedAccountMap = make(map[string]bool, len(tc.BlacklistedAccounts)) - for _, account := range tc.BlacklistedAccounts { - blacklistedAccountMap[account] = true - } - } - return blacklistedAccountMap -} - // exchangeTestWrapper is a wrapper that asserts the openrtb2 bid request just before the HoldAuction call type exchangeTestWrapper struct { ex exchange.Exchange @@ -1278,10 +1263,10 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han storedResponseFetcher = empty_fetcher.EmptyFetcher{} } - var accountFetcher stored_requests.AccountFetcher - accountFetcher = &mockAccountFetcher{ + accountFetcher := &mockAccountFetcher{ data: map[string]json.RawMessage{ "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), + "disabled_acct": json.RawMessage(`{"disabled":true}`), }, } diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index a2a519fa8df..7b2a7a5295a 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -244,7 +244,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re for _, podEr := range podErrors { resPodErr = append(resPodErr, strings.Join(podEr.ErrMsgs, ", ")) } - err := errors.New(fmt.Sprintf("all pods are incorrect: %s", strings.Join(resPodErr, "; "))) + err := fmt.Errorf("all pods are incorrect: %s", strings.Join(resPodErr, "; ")) errL = append(errL, err) handleError(&labels, w, errL, &vo, &debugLog) return @@ -403,7 +403,7 @@ func handleError(labels *metrics.Labels, w http.ResponseWriter, errL []error, vo var status int = http.StatusInternalServerError for _, er := range errL { erVal := errortypes.ReadCode(er) - if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.BlacklistedAcctErrorCode { + if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.AccountDisabledErrorCode { status = http.StatusServiceUnavailable labels.RequestStatus = metrics.RequestStatusBlacklisted break @@ -555,7 +555,7 @@ func buildVideoResponse(bidresponse *openrtb2.BidResponse, podErrors []PodError) if adPod == nil { adPod = &openrtb_ext.AdPod{ PodId: podId, - Targeting: make([]openrtb_ext.VideoTargeting, 0, 0), + Targeting: make([]openrtb_ext.VideoTargeting, 0), } adPods = append(adPods, adPod) } @@ -620,22 +620,15 @@ func getVideoStoredRequestId(request []byte) (string, error) { func mergeData(videoRequest *openrtb_ext.BidRequestVideo, bidRequest *openrtb2.BidRequest) error { if videoRequest.Site != nil { bidRequest.Site = videoRequest.Site - if &videoRequest.Content != nil { - bidRequest.Site.Content = &videoRequest.Content - } + bidRequest.Site.Content = &videoRequest.Content } if videoRequest.App != nil { bidRequest.App = videoRequest.App - if &videoRequest.Content != nil { - bidRequest.App.Content = &videoRequest.Content - } - } - - if &videoRequest.Device != nil { - bidRequest.Device = &videoRequest.Device + bidRequest.App.Content = &videoRequest.Content } + bidRequest.Device = &videoRequest.Device bidRequest.User = videoRequest.User if len(videoRequest.BCat) != 0 { @@ -768,7 +761,7 @@ func (deps *endpointDeps) validateVideoRequest(req *openrtb_ext.BidRequestVideo) err := errors.New("request missing required field: PodConfig.Pods") errL = append(errL, err) } - podErrors := make([]PodError, 0, 0) + podErrors := make([]PodError, 0) podIdsSet := make(map[int]bool) for ind, pod := range req.PodConfig.Pods { podErr := PodError{} diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index da4522ebe0c..f3135ff48db 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -292,7 +292,7 @@ func TestVideoEndpointNoPods(t *testing.T) { deps := mockDeps(t, ex) deps.VideoAuctionEndpoint(recorder, req, nil) - errorMessage := string(recorder.Body.Bytes()) + errorMessage := recorder.Body.String() assert.Equal(t, recorder.Code, 500, "Should catch error in request") assert.Equal(t, "Critical error while running the video endpoint: request missing required field: PodConfig.DurationRangeSec request missing required field: PodConfig.Pods", errorMessage, "Incorrect request validation message") @@ -739,7 +739,7 @@ func TestVideoBuildVideoResponsePodErrors(t *testing.T) { func TestVideoBuildVideoResponseNoBids(t *testing.T) { openRtbBidResp := openrtb2.BidResponse{} - podErrors := make([]PodError, 0, 0) + podErrors := make([]PodError, 0) openRtbBidResp.SeatBid = make([]openrtb2.SeatBid, 0) bidRespVideo, err := buildVideoResponse(&openRtbBidResp, podErrors) assert.NoError(t, err, "Error should be nil") @@ -808,7 +808,7 @@ func TestHandleError(t *testing.T) { { description: "Blocked account - return 503 with blocked metrics status", giveErrors: []error{ - &errortypes.BlacklistedAcct{}, + &errortypes.AccountDisabled{}, }, wantCode: 503, wantMetricsStatus: metrics.RequestStatusBlacklisted, @@ -1275,40 +1275,6 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { } } -func mockDepsInvalidPrivacy(t *testing.T, ex *mockExchangeVideo) *endpointDeps { - return &endpointDeps{ - fakeUUIDGenerator{}, - ex, - mockBidderParamValidator{}, - &mockVideoStoredReqFetcher{}, - &mockVideoStoredReqFetcher{}, - &mockAccountFetcher{data: mockVideoAccountData}, - &config.Configuration{MaxRequestSize: maxSize, - AccountDefaults: config.Account{ - Privacy: config.AccountPrivacy{ - AllowActivities: &config.AllowActivities{ - TransmitPreciseGeo: config.Activity{Rules: []config.ActivityRule{ - {Condition: config.ActivityCondition{ComponentName: []string{"bidderA.BidderB.bidderC"}}}, - }}, - }, - }, - }, - }, - &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - false, - []byte{}, - openrtb_ext.BuildBidderMap(), - ex.cache, - regexp.MustCompile(`[<>]`), - hardcodedResponseIPValidator{response: true}, - empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}, - nil, - } -} - func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) *endpointDeps { deps := &endpointDeps{ fakeUUIDGenerator{}, diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 897538c35e5..18099193045 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -1244,27 +1244,6 @@ func TestSetUIDEndpointMetrics(t *testing.T) { a.On("LogSetUIDObject", &expected).Once() }, }, - { - description: "Blocked account", - uri: "/setuid?bidder=pubmatic&uid=123&account=blocked_acct", - cookies: []*usersync.Cookie{}, - syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, - gdprAllowsHostCookies: true, - expectedResponseCode: 400, - expectedMetrics: func(m *metrics.MetricsEngineMock) { - m.On("RecordSetUid", metrics.SetUidAccountBlocked).Once() - }, - expectedAnalytics: func(a *MockAnalytics) { - expected := analytics.SetUIDObject{ - Status: 400, - Bidder: "pubmatic", - UID: "", - Errors: []error{errCookieSyncAccountBlocked}, - Success: false, - } - a.On("LogSetUIDObject", &expected).Once() - }, - }, { description: "Invalid account", uri: "/setuid?bidder=pubmatic&uid=123&account=unknown", @@ -1549,9 +1528,6 @@ func makeRequest(uri string, existingSyncs map[string]string) *http.Request { func doRequest(req *http.Request, analytics analytics.PBSAnalyticsModule, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError, cfgAccountRequired bool, maxCookieSize int, priorityGroups [][]string) *httptest.ResponseRecorder { cfg := config.Configuration{ AccountRequired: cfgAccountRequired, - BlacklistedAcctMap: map[string]bool{ - "blocked_acct": true, - }, AccountDefaults: config.Account{}, UserSync: config.UserSync{ PriorityGroups: priorityGroups, diff --git a/errortypes/code.go b/errortypes/code.go index ef8287f2d7d..5cbf3f860b4 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -9,7 +9,7 @@ const ( BadServerResponseErrorCode FailedToRequestBidsErrorCode BidderTemporarilyDisabledErrorCode - BlacklistedAcctErrorCode + AccountDisabledErrorCode AcctRequiredErrorCode NoConversionRateErrorCode MalformedAcctErrorCode diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index 01a5cca4af7..18b70634c48 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -79,23 +79,20 @@ func (err *BlacklistedApp) Severity() Severity { return SeverityFatal } -// BlacklistedAcct should be used when a request account ID matches an entry in the BlacklistedAccts -// environment variable array -// -// These errors will be written to http.ResponseWriter before canceling execution -type BlacklistedAcct struct { +// AccountDisabled should be used when a request an account is specifically disabled in account config. +type AccountDisabled struct { Message string } -func (err *BlacklistedAcct) Error() string { +func (err *AccountDisabled) Error() string { return err.Message } -func (err *BlacklistedAcct) Code() int { - return BlacklistedAcctErrorCode +func (err *AccountDisabled) Code() int { + return AccountDisabledErrorCode } -func (err *BlacklistedAcct) Severity() Severity { +func (err *AccountDisabled) Severity() Severity { return SeverityFatal } From a2e387f36d48a846bedc6020d772239bba314372 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:26:50 -0400 Subject: [PATCH 032/138] Remove Config Backwards Compatibility: Account GDPR/CCPA Integration Enabled (#3155) --- account/account.go | 20 --- account/account_test.go | 103 ------------- config/account.go | 18 +-- config/account_test.go | 203 ++++++++------------------ metrics/config/metrics.go | 25 ---- metrics/go_metrics.go | 30 ---- metrics/go_metrics_test.go | 75 ---------- metrics/metrics.go | 3 - metrics/metrics_mock.go | 12 -- metrics/prometheus/prometheus.go | 34 ----- metrics/prometheus/prometheus_test.go | 67 --------- 11 files changed, 64 insertions(+), 526 deletions(-) diff --git a/account/account.go b/account/account.go index 70527d0f6ee..b4d0c334d6b 100644 --- a/account/account.go +++ b/account/account.go @@ -50,18 +50,6 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r Message: fmt.Sprintf("The prebid-server account config for account id \"%s\" is malformed. Please reach out to the prebid server host.", accountID), }} } - usingGDPRChannelEnabled := useGDPRChannelEnabled(account) - usingCCPAChannelEnabled := useCCPAChannelEnabled(account) - - if usingGDPRChannelEnabled { - me.RecordAccountGDPRChannelEnabledWarning(accountID) - } - if usingCCPAChannelEnabled { - me.RecordAccountCCPAChannelEnabledWarning(accountID) - } - if usingGDPRChannelEnabled || usingCCPAChannelEnabled { - me.RecordAccountUpgradeStatus(accountID) - } if err != nil { errs = append(errs, err) @@ -154,11 +142,3 @@ func setDerivedConfig(account *config.Account) { } } } - -func useGDPRChannelEnabled(account *config.Account) bool { - return account.GDPR.ChannelEnabled.IsSet() && !account.GDPR.IntegrationEnabled.IsSet() -} - -func useCCPAChannelEnabled(account *config.Account) bool { - return account.CCPA.ChannelEnabled.IsSet() && !account.CCPA.IntegrationEnabled.IsSet() -} diff --git a/account/account_test.go b/account/account_test.go index da0457f8d47..7a242f21188 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -214,106 +214,3 @@ func TestSetDerivedConfig(t *testing.T) { assert.Equal(t, account.GDPR.Purpose1.EnforceAlgoID, tt.wantEnforceAlgoID, tt.description) } } - -func TestGdprCcpaChannelEnabledMetrics(t *testing.T) { - cfg := &config.Configuration{} - fetcher := &mockAccountFetcher{} - assert.NoError(t, cfg.MarshalAccountDefaults()) - - testCases := []struct { - name string - givenAccountID string - givenMetric string - expectedMetricCount int - }{ - { - name: "ChannelEnabledGDPR", - givenAccountID: "gdpr_channel_enabled_acct", - givenMetric: "RecordAccountGDPRChannelEnabledWarning", - expectedMetricCount: 1, - }, - { - name: "ChannelEnabledCCPA", - givenAccountID: "ccpa_channel_enabled_acct", - givenMetric: "RecordAccountCCPAChannelEnabledWarning", - expectedMetricCount: 1, - }, - { - name: "NotChannelEnabledCCPA", - givenAccountID: "valid_acct", - givenMetric: "RecordAccountCCPAChannelEnabledWarning", - expectedMetricCount: 0, - }, - { - name: "NotChannelEnabledGDPR", - givenAccountID: "valid_acct", - givenMetric: "RecordAccountGDPRChannelEnabledWarning", - expectedMetricCount: 0, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - metrics := &metrics.MetricsEngineMock{} - metrics.Mock.On(test.givenMetric, mock.Anything, mock.Anything).Return() - metrics.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return() - - _, _ = GetAccount(context.Background(), cfg, fetcher, test.givenAccountID, metrics) - - metrics.AssertNumberOfCalls(t, test.givenMetric, test.expectedMetricCount) - }) - } -} - -func TestAccountUpgradeStatusGetAccount(t *testing.T) { - cfg := &config.Configuration{} - fetcher := &mockAccountFetcher{} - assert.NoError(t, cfg.MarshalAccountDefaults()) - - testCases := []struct { - name string - givenAccountIDs []string - givenMetrics []string - expectedMetricCount int - }{ - { - name: "ZeroDeprecatedConfigs", - givenAccountIDs: []string{"valid_acct"}, - givenMetrics: []string{}, - expectedMetricCount: 0, - }, - { - name: "OneDeprecatedConfigGDPRChannelEnabled", - givenAccountIDs: []string{"gdpr_channel_enabled_acct"}, - givenMetrics: []string{"RecordAccountGDPRChannelEnabledWarning"}, - expectedMetricCount: 1, - }, - { - name: "OneDeprecatedConfigCCPAChannelEnabled", - givenAccountIDs: []string{"ccpa_channel_enabled_acct"}, - givenMetrics: []string{"RecordAccountCCPAChannelEnabledWarning"}, - expectedMetricCount: 1, - }, - { - name: "MultipleAccountsWithDeprecatedConfigs", - givenAccountIDs: []string{"gdpr_channel_enabled_acct", "ccpa_channel_enabled_acct"}, - givenMetrics: []string{"RecordAccountGDPRChannelEnabledWarning", "RecordAccountCCPAChannelEnabledWarning"}, - expectedMetricCount: 2, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - metrics := &metrics.MetricsEngineMock{} - for _, metric := range test.givenMetrics { - metrics.Mock.On(metric, mock.Anything, mock.Anything).Return() - } - metrics.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return() - - for _, accountID := range test.givenAccountIDs { - _, _ = GetAccount(context.Background(), cfg, fetcher, accountID, metrics) - } - metrics.AssertNumberOfCalls(t, "RecordAccountUpgradeStatus", test.expectedMetricCount) - }) - } -} diff --git a/config/account.go b/config/account.go index d57fdb90773..92af6c8a97f 100644 --- a/config/account.go +++ b/config/account.go @@ -53,9 +53,8 @@ type CookieSync struct { // AccountCCPA represents account-specific CCPA configuration type AccountCCPA struct { - Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` - IntegrationEnabled AccountChannel `mapstructure:"integration_enabled" json:"integration_enabled"` - ChannelEnabled AccountChannel `mapstructure:"channel_enabled" json:"channel_enabled"` + Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` + ChannelEnabled AccountChannel `mapstructure:"channel_enabled" json:"channel_enabled"` } type AccountPriceFloors struct { @@ -94,17 +93,14 @@ func (pf *AccountPriceFloors) IsAdjustForBidAdjustmentEnabled() bool { func (a *AccountCCPA) EnabledForChannelType(channelType ChannelType) *bool { if channelEnabled := a.ChannelEnabled.GetByChannelType(channelType); channelEnabled != nil { return channelEnabled - } else if integrationEnabled := a.IntegrationEnabled.GetByChannelType(channelType); integrationEnabled != nil { - return integrationEnabled } return a.Enabled } // AccountGDPR represents account-specific GDPR configuration type AccountGDPR struct { - Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` - IntegrationEnabled AccountChannel `mapstructure:"integration_enabled" json:"integration_enabled"` - ChannelEnabled AccountChannel `mapstructure:"channel_enabled" json:"channel_enabled"` + Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` + ChannelEnabled AccountChannel `mapstructure:"channel_enabled" json:"channel_enabled"` // Array of basic enforcement vendors that is used to create the hash table so vendor names can be instantly accessed BasicEnforcementVendors []string `mapstructure:"basic_enforcement_vendors" json:"basic_enforcement_vendors"` BasicEnforcementVendorsMap map[string]struct{} @@ -129,8 +125,6 @@ type AccountGDPR struct { func (a *AccountGDPR) EnabledForChannelType(channelType ChannelType) *bool { if channelEnabled := a.ChannelEnabled.GetByChannelType(channelType); channelEnabled != nil { return channelEnabled - } else if integrationEnabled := a.IntegrationEnabled.GetByChannelType(channelType); integrationEnabled != nil { - return integrationEnabled } return a.Enabled } @@ -298,10 +292,6 @@ func (m AccountModules) ModuleConfig(id string) (json.RawMessage, error) { return m[vendor][module], nil } -func (a *AccountChannel) IsSet() bool { - return a.AMP != nil || a.App != nil || a.Video != nil || a.Web != nil || a.DOOH != nil -} - type AccountPrivacy struct { AllowActivities *AllowActivities `mapstructure:"allowactivities" json:"allowactivities"` IPv6Config IPv6 `mapstructure:"ipv6" json:"ipv6"` diff --git a/config/account_test.go b/config/account_test.go index 58f2a52e645..d0c8507deef 100644 --- a/config/account_test.go +++ b/config/account_test.go @@ -14,68 +14,46 @@ func TestAccountGDPREnabledForChannelType(t *testing.T) { trueValue, falseValue := true, false tests := []struct { - description string - giveChannelType ChannelType - giveGDPREnabled *bool - giveWebGDPREnabled *bool - giveWebGDPREnabledForIntegration *bool - wantEnabled *bool + description string + giveChannelType ChannelType + giveGDPREnabled *bool + giveWebGDPREnabled *bool + wantEnabled *bool }{ { - description: "GDPR Web channel enabled, general GDPR disabled", - giveChannelType: ChannelWeb, - giveGDPREnabled: &falseValue, - giveWebGDPREnabled: &trueValue, - giveWebGDPREnabledForIntegration: nil, - wantEnabled: &trueValue, + description: "GDPR Web channel enabled, general GDPR disabled", + giveChannelType: ChannelWeb, + giveGDPREnabled: &falseValue, + giveWebGDPREnabled: &trueValue, + wantEnabled: &trueValue, }, { - description: "GDPR Web channel disabled, general GDPR enabled", - giveChannelType: ChannelWeb, - giveGDPREnabled: &trueValue, - giveWebGDPREnabled: &falseValue, - giveWebGDPREnabledForIntegration: nil, - wantEnabled: &falseValue, + description: "GDPR Web channel disabled, general GDPR enabled", + giveChannelType: ChannelWeb, + giveGDPREnabled: &trueValue, + giveWebGDPREnabled: &falseValue, + wantEnabled: &falseValue, }, { - description: "GDPR Web channel unspecified, general GDPR disabled", - giveChannelType: ChannelWeb, - giveGDPREnabled: &falseValue, - giveWebGDPREnabled: nil, - giveWebGDPREnabledForIntegration: nil, - wantEnabled: &falseValue, + description: "GDPR Web channel unspecified, general GDPR disabled", + giveChannelType: ChannelWeb, + giveGDPREnabled: &falseValue, + giveWebGDPREnabled: nil, + wantEnabled: &falseValue, }, { - description: "GDPR Web channel unspecified, general GDPR enabled", - giveChannelType: ChannelWeb, - giveGDPREnabled: &trueValue, - giveWebGDPREnabled: nil, - giveWebGDPREnabledForIntegration: nil, - wantEnabled: &trueValue, + description: "GDPR Web channel unspecified, general GDPR enabled", + giveChannelType: ChannelWeb, + giveGDPREnabled: &trueValue, + giveWebGDPREnabled: nil, + wantEnabled: &trueValue, }, { - description: "GDPR Web channel unspecified, general GDPR unspecified", - giveChannelType: ChannelWeb, - giveGDPREnabled: nil, - giveWebGDPREnabled: nil, - giveWebGDPREnabledForIntegration: nil, - wantEnabled: nil, - }, - { - description: "Inegration Enabled is set, and channel enabled isn't", - giveChannelType: ChannelWeb, - giveGDPREnabled: &falseValue, - giveWebGDPREnabled: nil, - giveWebGDPREnabledForIntegration: &trueValue, - wantEnabled: &trueValue, - }, - { - description: "Inegration Enabled is set, and channel enabled is set, channel should have precedence", - giveChannelType: ChannelWeb, - giveGDPREnabled: &falseValue, - giveWebGDPREnabled: &trueValue, - giveWebGDPREnabledForIntegration: &falseValue, - wantEnabled: &trueValue, + description: "GDPR Web channel unspecified, general GDPR unspecified", + giveChannelType: ChannelWeb, + giveGDPREnabled: nil, + giveWebGDPREnabled: nil, + wantEnabled: nil, }, } @@ -86,9 +64,6 @@ func TestAccountGDPREnabledForChannelType(t *testing.T) { ChannelEnabled: AccountChannel{ Web: tt.giveWebGDPREnabled, }, - IntegrationEnabled: AccountChannel{ - Web: tt.giveWebGDPREnabledForIntegration, - }, }, } @@ -107,68 +82,46 @@ func TestAccountCCPAEnabledForChannelType(t *testing.T) { trueValue, falseValue := true, false tests := []struct { - description string - giveChannelType ChannelType - giveCCPAEnabled *bool - giveWebCCPAEnabled *bool - giveWebCCPAEnabledForIntegration *bool - wantEnabled *bool + description string + giveChannelType ChannelType + giveCCPAEnabled *bool + giveWebCCPAEnabled *bool + wantEnabled *bool }{ { - description: "CCPA Web channel enabled, general CCPA disabled", - giveChannelType: ChannelWeb, - giveCCPAEnabled: &falseValue, - giveWebCCPAEnabled: &trueValue, - giveWebCCPAEnabledForIntegration: nil, - wantEnabled: &trueValue, - }, - { - description: "CCPA Web channel disabled, general CCPA enabled", - giveChannelType: ChannelWeb, - giveCCPAEnabled: &trueValue, - giveWebCCPAEnabled: &falseValue, - giveWebCCPAEnabledForIntegration: nil, - wantEnabled: &falseValue, + description: "CCPA Web channel enabled, general CCPA disabled", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &falseValue, + giveWebCCPAEnabled: &trueValue, + wantEnabled: &trueValue, }, { - description: "CCPA Web channel unspecified, general CCPA disabled", - giveChannelType: ChannelWeb, - giveCCPAEnabled: &falseValue, - giveWebCCPAEnabled: nil, - giveWebCCPAEnabledForIntegration: nil, - wantEnabled: &falseValue, + description: "CCPA Web channel disabled, general CCPA enabled", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &trueValue, + giveWebCCPAEnabled: &falseValue, + wantEnabled: &falseValue, }, { - description: "CCPA Web channel unspecified, general CCPA enabled", - giveChannelType: ChannelWeb, - giveCCPAEnabled: &trueValue, - giveWebCCPAEnabled: nil, - giveWebCCPAEnabledForIntegration: nil, - wantEnabled: &trueValue, + description: "CCPA Web channel unspecified, general CCPA disabled", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &falseValue, + giveWebCCPAEnabled: nil, + wantEnabled: &falseValue, }, { - description: "CCPA Web channel unspecified, general CCPA unspecified", - giveChannelType: ChannelWeb, - giveCCPAEnabled: nil, - giveWebCCPAEnabled: nil, - giveWebCCPAEnabledForIntegration: nil, - wantEnabled: nil, + description: "CCPA Web channel unspecified, general CCPA enabled", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &trueValue, + giveWebCCPAEnabled: nil, + wantEnabled: &trueValue, }, { - description: "Inegration Enabled is set, and channel enabled isn't", - giveChannelType: ChannelWeb, - giveCCPAEnabled: &falseValue, - giveWebCCPAEnabled: nil, - giveWebCCPAEnabledForIntegration: &trueValue, - wantEnabled: &trueValue, - }, - { - description: "Inegration Enabled is set, and channel enabled is set, channel should have precedence", - giveChannelType: ChannelWeb, - giveCCPAEnabled: &falseValue, - giveWebCCPAEnabled: &trueValue, - giveWebCCPAEnabledForIntegration: &falseValue, - wantEnabled: &trueValue, + description: "CCPA Web channel unspecified, general CCPA unspecified", + giveChannelType: ChannelWeb, + giveCCPAEnabled: nil, + giveWebCCPAEnabled: nil, + wantEnabled: nil, }, } @@ -179,9 +132,6 @@ func TestAccountCCPAEnabledForChannelType(t *testing.T) { ChannelEnabled: AccountChannel{ Web: tt.giveWebCCPAEnabled, }, - IntegrationEnabled: AccountChannel{ - Web: tt.giveWebCCPAEnabledForIntegration, - }, }, } @@ -844,39 +794,6 @@ func TestModulesGetConfig(t *testing.T) { } } -func TestAccountChannelIsSet(t *testing.T) { - trueBool := true - falseBool := false - - testCases := []struct { - name string - givenAccountChannel *AccountChannel - expected bool - }{ - { - name: "AccountChannelSetAllFields", - givenAccountChannel: &AccountChannel{AMP: &trueBool, App: &falseBool, Video: &falseBool, Web: &falseBool, DOOH: &falseBool}, - expected: true, - }, - { - name: "AccountChannelNotSet", - givenAccountChannel: &AccountChannel{}, - expected: false, - }, - { - name: "AccountChannelSetAmpOnly", - givenAccountChannel: &AccountChannel{AMP: &trueBool}, - expected: true, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.expected, test.givenAccountChannel.IsSet()) - }) - } -} - func TestAccountPriceFloorsValidate(t *testing.T) { tests := []struct { diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 757cde336b2..d5cb00344ec 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -322,22 +322,6 @@ func (me *MultiMetricsEngine) RecordBidValidationSecureMarkupWarn(adapter openrt } } -func (me *MultiMetricsEngine) RecordAccountGDPRChannelEnabledWarning(account string) { - for _, thisME := range *me { - thisME.RecordAccountGDPRChannelEnabledWarning(account) - } -} -func (me *MultiMetricsEngine) RecordAccountCCPAChannelEnabledWarning(account string) { - for _, thisME := range *me { - thisME.RecordAccountCCPAChannelEnabledWarning(account) - } -} -func (me *MultiMetricsEngine) RecordAccountUpgradeStatus(account string) { - for _, thisME := range *me { - thisME.RecordAccountUpgradeStatus(account) - } -} - func (me *MultiMetricsEngine) RecordModuleCalled(labels metrics.ModuleLabels, duration time.Duration) { for _, thisME := range *me { thisME.RecordModuleCalled(labels, duration) @@ -531,15 +515,6 @@ func (me *NilMetricsEngine) RecordBidValidationSecureMarkupError(adapter openrtb func (me *NilMetricsEngine) RecordBidValidationSecureMarkupWarn(adapter openrtb_ext.BidderName, account string) { } -func (me *NilMetricsEngine) RecordAccountGDPRChannelEnabledWarning(account string) { -} - -func (me *NilMetricsEngine) RecordAccountCCPAChannelEnabledWarning(account string) { -} - -func (me *NilMetricsEngine) RecordAccountUpgradeStatus(account string) { -} - func (me *NilMetricsEngine) RecordModuleCalled(labels metrics.ModuleLabels, duration time.Duration) { } diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 37080b05456..0d60a7009d9 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -125,11 +125,6 @@ type accountMetrics struct { bidValidationCreativeSizeWarnMeter metrics.Meter bidValidationSecureMarkupMeter metrics.Meter bidValidationSecureMarkupWarnMeter metrics.Meter - - // Account Deprciation Metrics - channelEnabledGDPRMeter metrics.Meter - channelEnabledCCPAMeter metrics.Meter - accountDeprecationSummaryMeter metrics.Meter } type ModuleMetrics struct { @@ -566,10 +561,6 @@ func (me *Metrics) getAccountMetrics(id string) *accountMetrics { am.bidValidationSecureMarkupMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.response.validation.secure.err", id), me.MetricsRegistry) am.bidValidationSecureMarkupWarnMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.response.validation.secure.warn", id), me.MetricsRegistry) - am.channelEnabledCCPAMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.ccpa.channel_enabled.warn", id), me.MetricsRegistry) - am.channelEnabledGDPRMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.channel_enabled.warn", id), me.MetricsRegistry) - am.accountDeprecationSummaryMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.summary", id), me.MetricsRegistry) - if !me.MetricsDisabled.AccountModulesMetrics { for _, mod := range me.modules { am.moduleMetrics[mod] = makeBlankModuleMetrics() @@ -615,27 +606,6 @@ func (me *Metrics) RecordDebugRequest(debugEnabled bool, pubID string) { } } -func (me *Metrics) RecordAccountGDPRChannelEnabledWarning(account string) { - if account != PublisherUnknown { - am := me.getAccountMetrics(account) - am.channelEnabledGDPRMeter.Mark(1) - } -} - -func (me *Metrics) RecordAccountCCPAChannelEnabledWarning(account string) { - if account != PublisherUnknown { - am := me.getAccountMetrics(account) - am.channelEnabledCCPAMeter.Mark(1) - } -} - -func (me *Metrics) RecordAccountUpgradeStatus(account string) { - if account != PublisherUnknown { - am := me.getAccountMetrics(account) - am.accountDeprecationSummaryMeter.Mark(1) - } -} - func (me *Metrics) RecordStoredResponse(pubId string) { me.StoredResponsesMeter.Mark(1) if pubId != PublisherUnknown && !me.MetricsDisabled.AccountStoredResponses { diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 3564f9fa2a5..8acd1923a85 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -1066,81 +1066,6 @@ func TestRecordModuleAccountMetrics(t *testing.T) { } } -func TestRecordAccountGDPRChannelEnabledWarningMetrics(t *testing.T) { - testCases := []struct { - name string - givenPubID string - expectedMetricCount int64 - }{ - { - name: "GdprChannelMetricIncremented", - givenPubID: "acct-id", - expectedMetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) - - m.RecordAccountGDPRChannelEnabledWarning(test.givenPubID) - am := m.getAccountMetrics(test.givenPubID) - - assert.Equal(t, test.expectedMetricCount, am.channelEnabledGDPRMeter.Count()) - }) - } -} - -func TestRecordAccountCCPAChannelEnabledWarningMetrics(t *testing.T) { - testCases := []struct { - name string - givenPubID string - expectedMetricCount int64 - }{ - { - name: "CcpaChannelMetricIncremented", - givenPubID: "acct-id", - expectedMetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) - - m.RecordAccountCCPAChannelEnabledWarning(test.givenPubID) - am := m.getAccountMetrics(test.givenPubID) - - assert.Equal(t, test.expectedMetricCount, am.channelEnabledCCPAMeter.Count()) - }) - } -} - -func TestRecordAccountUpgradeStatusMetrics(t *testing.T) { - testCases := []struct { - name string - givenPubID string - expectedMetricCount int64 - }{ - { - name: "AccountDeprecationMeterIncremented", - givenPubID: "acct-id", - expectedMetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) - - m.RecordAccountUpgradeStatus(test.givenPubID) - am := m.getAccountMetrics(test.givenPubID) - - assert.Equal(t, test.expectedMetricCount, am.accountDeprecationSummaryMeter.Count()) - }) - } -} - func TestRecordOverheadTime(t *testing.T) { testCases := []struct { name string diff --git a/metrics/metrics.go b/metrics/metrics.go index 7315fa72712..99e64af0835 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -470,7 +470,4 @@ type MetricsEngine interface { RecordModuleSuccessRejected(labels ModuleLabels) RecordModuleExecutionError(labels ModuleLabels) RecordModuleTimeout(labels ModuleLabels) - RecordAccountGDPRChannelEnabledWarning(account string) - RecordAccountCCPAChannelEnabledWarning(account string) - RecordAccountUpgradeStatus(account string) } diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index f6ecf57e37f..eb092f0d972 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -221,15 +221,3 @@ func (me *MetricsEngineMock) RecordModuleExecutionError(labels ModuleLabels) { func (me *MetricsEngineMock) RecordModuleTimeout(labels ModuleLabels) { me.Called(labels) } - -func (me *MetricsEngineMock) RecordAccountGDPRChannelEnabledWarning(account string) { - me.Called(account) -} - -func (me *MetricsEngineMock) RecordAccountCCPAChannelEnabledWarning(account string) { - me.Called(account) -} - -func (me *MetricsEngineMock) RecordAccountUpgradeStatus(account string) { - me.Called(account) -} diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 363e5527fbc..30fb6c5f774 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -88,11 +88,6 @@ type Metrics struct { accountBidResponseSecureMarkupError *prometheus.CounterVec accountBidResponseSecureMarkupWarn *prometheus.CounterVec - // Account Deprecation Metrics - channelEnabledGDPR prometheus.Counter - channelEnabledCCPA prometheus.Counter - accountDeprecationSummary prometheus.Counter - // Module Metrics as a map where the key is the module name moduleDuration map[string]*prometheus.HistogramVec moduleCalls map[string]*prometheus.CounterVec @@ -502,17 +497,6 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "Count of AdsCert request, and if they were successfully sent.", []string{successLabel}) - metrics.channelEnabledCCPA = newCounterWithoutLabels(cfg, reg, - "account_config_ccpa_channel_enabled_warn", - "Count of requests referencing an account whose config specifies a depreceated ccpa.channel_enabled field") - metrics.channelEnabledGDPR = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_channel_enabled_warn", - "Count of requests referencing an account whose config specifies a depreceated gdpr.channel_enabled field") - - metrics.accountDeprecationSummary = newCounterWithoutLabels(cfg, reg, - "account_config_summary", - "Count of deprecated account config fields encountered across all accounts") - createModulesMetrics(cfg, reg, &metrics, moduleStageNames, standardTimeBuckets) metrics.Gatherer = reg @@ -693,24 +677,6 @@ func (m *Metrics) RecordDebugRequest(debugEnabled bool, pubID string) { } } -func (m *Metrics) RecordAccountGDPRChannelEnabledWarning(account string) { - if account != metrics.PublisherUnknown { - m.channelEnabledGDPR.Inc() - } -} - -func (m *Metrics) RecordAccountCCPAChannelEnabledWarning(account string) { - if account != metrics.PublisherUnknown { - m.channelEnabledCCPA.Inc() - } -} - -func (m *Metrics) RecordAccountUpgradeStatus(account string) { - if account != metrics.PublisherUnknown { - m.accountDeprecationSummary.Inc() - } -} - func (m *Metrics) RecordStoredResponse(pubId string) { m.storedResponses.Inc() if !m.metricsDisabled.AccountStoredResponses && pubId != metrics.PublisherUnknown { diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 90d331fe5ae..e57ae5e198a 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -1976,70 +1976,3 @@ func TestRecordModuleMetrics(t *testing.T) { } } } - -func TestRecordAccountGDPRChannelEnabledWarningMetrics(t *testing.T) { - testCases := []struct { - name string - givenPubID string - expectedMetricCount float64 - }{ - { - name: "GdprChannelMetricIncremented", - givenPubID: "acct-id", - expectedMetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - m := createMetricsForTesting() - m.RecordAccountGDPRChannelEnabledWarning(test.givenPubID) - - assertCounterValue(t, "", "GDPR Channel Enabled Deprecation Warnings", m.channelEnabledGDPR, test.expectedMetricCount) - }) - } -} - -func TestRecordAccountCCPAChannelEnabledWarningMetrics(t *testing.T) { - testCases := []struct { - name string - givenPubID string - expectedMetricCount float64 - }{ - { - name: "CcpaChannelMetricIncremented", - givenPubID: "acct-id", - expectedMetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - m := createMetricsForTesting() - m.RecordAccountCCPAChannelEnabledWarning(test.givenPubID) - - assertCounterValue(t, "", "CCPA Channel Enabled Deprecation Warnings", m.channelEnabledCCPA, test.expectedMetricCount) - }) - } -} - -func TestRecordAccountUpgradeStatusMetrics(t *testing.T) { - testCases := []struct { - name string - givenPubID string - expectedMetricCount float64 - }{ - { - name: "AccountDeprecationMeterIncremented", - givenPubID: "acct-id", - expectedMetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - m := createMetricsForTesting() - m.RecordAccountUpgradeStatus(test.givenPubID) - - assertCounterValue(t, "", "Account Depreciation Summary Meter should be incremented", m.accountDeprecationSummary, test.expectedMetricCount) - }) - - } -} From 781dc7beb873df33be13183ac1fe74465c7fc50d Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 4 Oct 2023 12:25:14 -0400 Subject: [PATCH 033/138] Remove Config Backwards Compatibility: User Sync URL (#3163) --- config/bidderinfo.go | 53 --------------------- config/bidderinfo_test.go | 97 ++++++--------------------------------- config/config_test.go | 1 - 3 files changed, 15 insertions(+), 136 deletions(-) diff --git a/config/bidderinfo.go b/config/bidderinfo.go index 3b3f86d4ec9..f1f437c5c93 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -9,10 +9,8 @@ import ( "strings" "text/template" - "github.com/golang/glog" "github.com/prebid/prebid-server/macros" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/sliceutil" validator "github.com/asaskevich/govalidator" "gopkg.in/yaml.v3" @@ -38,9 +36,6 @@ type BidderInfo struct { Experiment BidderInfoExperiment `yaml:"experiment" mapstructure:"experiment"` - // needed for backwards compatibility - UserSyncURL string `yaml:"usersync_url" mapstructure:"usersync_url"` - // needed for Rubicon XAPI AdapterXAPI `yaml:"xapi" mapstructure:"xapi"` @@ -329,9 +324,6 @@ func processBidderAliases(aliasNillableFieldsByBidder map[string]aliasNillableFi syncer := Syncer{Key: syncerKey} aliasBidderInfo.Syncer = &syncer } - if aliasBidderInfo.UserSyncURL == "" { - aliasBidderInfo.UserSyncURL = parentBidderInfo.UserSyncURL - } if alias.Disabled == nil { aliasBidderInfo.Disabled = parentBidderInfo.Disabled } @@ -614,51 +606,6 @@ func applyBidderInfoConfigOverrides(configBidderInfos BidderInfos, fsBidderInfos bidderInfo.EndpointCompression = fsBidderCfg.EndpointCompression } - // validate and try to apply the legacy usersync_url configuration in attempt to provide - // an easier upgrade path. be warned, this will break if the bidder adds a second syncer - // type and will eventually be removed after we've given hosts enough time to upgrade to - // the new config. - if bidderInfo.UserSyncURL != "" { - if fsBidderCfg.Syncer == nil { - return nil, fmt.Errorf("adapters.%s.usersync_url cannot be applied, bidder does not define a user sync", strings.ToLower(bidderName)) - } - - endpointsCount := 0 - if bidderInfo.Syncer.IFrame != nil { - bidderInfo.Syncer.IFrame.URL = bidderInfo.UserSyncURL - endpointsCount++ - } - if bidderInfo.Syncer.Redirect != nil { - bidderInfo.Syncer.Redirect.URL = bidderInfo.UserSyncURL - endpointsCount++ - } - - // use Supports as a hint if there are no good defaults provided - if endpointsCount == 0 { - if sliceutil.ContainsStringIgnoreCase(bidderInfo.Syncer.Supports, "iframe") { - bidderInfo.Syncer.IFrame = &SyncerEndpoint{URL: bidderInfo.UserSyncURL} - endpointsCount++ - } - if sliceutil.ContainsStringIgnoreCase(bidderInfo.Syncer.Supports, "redirect") { - bidderInfo.Syncer.Redirect = &SyncerEndpoint{URL: bidderInfo.UserSyncURL} - endpointsCount++ - } - } - - if endpointsCount == 0 { - return nil, fmt.Errorf("adapters.%s.usersync_url cannot be applied, bidder does not define user sync endpoints and does not define supported endpoints", strings.ToLower(bidderName)) - } - - // if the bidder defines both an iframe and redirect endpoint, we can't be sure which config value to - // override, and it wouldn't be both. this is a fatal configuration error. - if endpointsCount > 1 { - return nil, fmt.Errorf("adapters.%s.usersync_url cannot be applied, bidder defines multiple user sync endpoints or supports multiple endpoints", strings.ToLower(bidderName)) - } - - // provide a warning that this compatibility layer is temporary - glog.Warningf("adapters.%s.usersync_url is deprecated and will be removed in a future version, please update to the latest user sync config values", strings.ToLower(bidderName)) - } - fsBidderInfos[string(normalizedBidderName)] = bidderInfo } else { return nil, fmt.Errorf("error finding configuration for bidder %s: unknown bidder", bidderName) diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go index 0900421a574..7b134314609 100644 --- a/config/bidderinfo_test.go +++ b/config/bidderinfo_test.go @@ -9,6 +9,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const testInfoFilesPathValid = "./test/bidder-info-valid" @@ -50,7 +51,6 @@ disabled: false extra_info: extra-info app_secret: app-secret platform_id: 123 -usersync_url: user-url userSync: key: foo default: iframe @@ -217,7 +217,6 @@ func TestProcessBidderInfo(t *testing.T) { UserMacro: "UID", }, }, - UserSyncURL: "user-url", XAPI: AdapterXAPI{ Username: "uname", Password: "pwd", @@ -263,7 +262,6 @@ func TestProcessBidderInfo(t *testing.T) { Syncer: &Syncer{ Key: "foo", }, - UserSyncURL: "user-url", XAPI: AdapterXAPI{ Username: "uname", Password: "pwd", @@ -273,6 +271,7 @@ func TestProcessBidderInfo(t *testing.T) { }, }, } + for _, test := range testCases { reader := StubInfoReader{test.bidderInfos} bidderInfos, err := processBidderInfos(reader, mockNormalizeBidderName) @@ -281,9 +280,7 @@ func TestProcessBidderInfo(t *testing.T) { } else { assert.Equal(t, test.expectedBidderInfos, bidderInfos, "incorrect bidder infos for test case: %s", test.description) } - } - } func TestProcessAliasBidderInfo(t *testing.T) { @@ -328,7 +325,6 @@ func TestProcessAliasBidderInfo(t *testing.T) { UserMacro: "UID", }, }, - UserSyncURL: "user-url", XAPI: AdapterXAPI{ Username: "uname", Password: "pwd", @@ -376,7 +372,6 @@ func TestProcessAliasBidderInfo(t *testing.T) { UserMacro: "alias-UID", }, }, - UserSyncURL: "alias-user-url", XAPI: AdapterXAPI{ Username: "alias-uname", Password: "alias-pwd", @@ -1493,78 +1488,15 @@ func TestSyncerEndpointOverride(t *testing.T) { } func TestApplyBidderInfoConfigSyncerOverrides(t *testing.T) { - var testCases = []struct { - description string - givenFsBidderInfos BidderInfos - givenConfigBidderInfos BidderInfos - expectedError string - expectedBidderInfos BidderInfos - }{ - { - description: "Syncer Override", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "original"}}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, - expectedBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, - }, - { - description: "UserSyncURL Override IFrame", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{IFrame: &SyncerEndpoint{URL: "original"}}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedBidderInfos: BidderInfos{"a": {UserSyncURL: "override", Syncer: &Syncer{IFrame: &SyncerEndpoint{URL: "override"}}}}, - }, - { - description: "UserSyncURL Supports IFrame", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Supports: []string{"iframe"}}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedBidderInfos: BidderInfos{"a": {UserSyncURL: "override", Syncer: &Syncer{Supports: []string{"iframe"}, IFrame: &SyncerEndpoint{URL: "override"}}}}, - }, - { - description: "UserSyncURL Override Redirect", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Supports: []string{"redirect"}}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedBidderInfos: BidderInfos{"a": {UserSyncURL: "override", Syncer: &Syncer{Supports: []string{"redirect"}, Redirect: &SyncerEndpoint{URL: "override"}}}}, - }, - { - description: "UserSyncURL Supports Redirect", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Redirect: &SyncerEndpoint{URL: "original"}}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedBidderInfos: BidderInfos{"a": {UserSyncURL: "override", Syncer: &Syncer{Redirect: &SyncerEndpoint{URL: "override"}}}}, - }, - { - description: "UserSyncURL Override Syncer Not Defined", - givenFsBidderInfos: BidderInfos{"a": {}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedError: "adapters.a.usersync_url cannot be applied, bidder does not define a user sync", - }, - { - description: "UserSyncURL Override Syncer Endpoints Not Defined", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedError: "adapters.a.usersync_url cannot be applied, bidder does not define user sync endpoints and does not define supported endpoints", - }, - { - description: "UserSyncURL Override Ambiguous", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{IFrame: &SyncerEndpoint{URL: "originalIFrame"}, Redirect: &SyncerEndpoint{URL: "originalRedirect"}}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedError: "adapters.a.usersync_url cannot be applied, bidder defines multiple user sync endpoints or supports multiple endpoints", - }, - { - description: "UserSyncURL Supports Ambiguous", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Supports: []string{"iframe", "redirect"}}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedError: "adapters.a.usersync_url cannot be applied, bidder defines multiple user sync endpoints or supports multiple endpoints", - }, - } + var ( + givenFileSystem = BidderInfos{"a": {Syncer: &Syncer{Key: "original"}}} + givenConfig = BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}} + expected = BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}} + ) - for _, test := range testCases { - bidderInfos, resultErr := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) - if test.expectedError == "" { - assert.NoError(t, resultErr, test.description+":err") - assert.Equal(t, test.expectedBidderInfos, bidderInfos, test.description+":result") - } else { - assert.EqualError(t, resultErr, test.expectedError, test.description+":err") - } - } + result, resultErr := applyBidderInfoConfigOverrides(givenConfig, givenFileSystem, mockNormalizeBidderName) + assert.NoError(t, resultErr) + assert.Equal(t, expected, result) } func TestApplyBidderInfoConfigOverrides(t *testing.T) { @@ -1754,10 +1686,12 @@ func TestApplyBidderInfoConfigOverridesInvalid(t *testing.T) { func TestReadFullYamlBidderConfig(t *testing.T) { bidder := "bidderA" bidderInf := BidderInfo{} + err := yaml.Unmarshal([]byte(fullBidderYAMLConfig), &bidderInf) - actualBidderInfo, err := applyBidderInfoConfigOverrides(BidderInfos{bidder: bidderInf}, BidderInfos{bidder: {Syncer: &Syncer{Supports: []string{"iframe"}}}}, mockNormalizeBidderName) + require.NoError(t, err) - assert.NoError(t, err, "Error wasn't expected") + actualBidderInfo, err := applyBidderInfoConfigOverrides(BidderInfos{bidder: bidderInf}, BidderInfos{bidder: {Syncer: &Syncer{Supports: []string{"iframe"}}}}, mockNormalizeBidderName) + require.NoError(t, err) expectedBidderInfo := BidderInfos{ bidder: { @@ -1794,11 +1728,10 @@ func TestReadFullYamlBidderConfig(t *testing.T) { ExtraAdapterInfo: "extra-info", AppSecret: "app-secret", PlatformID: "123", - UserSyncURL: "user-url", Syncer: &Syncer{ Key: "foo", IFrame: &SyncerEndpoint{ - URL: "user-url", + URL: "https://foo.com/sync?mode=iframe&r={{.RedirectURL}}", RedirectURL: "https://redirect/setuid/iframe", ExternalURL: "https://iframe.host", UserMacro: "UID", diff --git a/config/config_test.go b/config/config_test.go index 97be28eb6e3..21d1681c1e7 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -32,7 +32,6 @@ var bidderInfos = BidderInfos{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, }, }, - UserSyncURL: "http://bidder2.com/usersync", }, } From 134e9aa739da331754cfe4694f5fe8b767a1adb3 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Wed, 4 Oct 2023 16:33:06 -0700 Subject: [PATCH 034/138] Analytics activities (#3024) --- .../{config/config.go => build/build.go} | 47 ++++--- .../config_test.go => build/build_test.go} | 116 +++++++++++++++--- analytics/core.go | 4 +- analytics/filesystem/file_module.go | 2 +- analytics/pubstack/pubstack_module.go | 4 +- analytics/pubstack/pubstack_module_test.go | 14 +-- analytics/runner.go | 14 +++ endpoints/cookie_sync.go | 6 +- endpoints/cookie_sync_test.go | 44 +++---- endpoints/events/event.go | 9 +- endpoints/events/event_test.go | 9 +- endpoints/openrtb2/amp_auction.go | 12 +- endpoints/openrtb2/amp_auction_test.go | 37 +++--- endpoints/openrtb2/auction.go | 15 +-- endpoints/openrtb2/auction_benchmark_test.go | 4 +- endpoints/openrtb2/auction_test.go | 66 +++++----- endpoints/openrtb2/test_utils.go | 6 +- endpoints/openrtb2/video_auction.go | 10 +- endpoints/openrtb2/video_auction_test.go | 18 +-- endpoints/setuid.go | 4 +- endpoints/setuid_test.go | 34 ++--- router/router.go | 16 +-- 22 files changed, 307 insertions(+), 184 deletions(-) rename analytics/{config/config.go => build/build.go} (55%) rename analytics/{config/config_test.go => build/build_test.go} (50%) create mode 100644 analytics/runner.go diff --git a/analytics/config/config.go b/analytics/build/build.go similarity index 55% rename from analytics/config/config.go rename to analytics/build/build.go index 557fec361dc..6fb48705981 100644 --- a/analytics/config/config.go +++ b/analytics/build/build.go @@ -1,4 +1,4 @@ -package config +package build import ( "github.com/benbjohnson/clock" @@ -8,14 +8,15 @@ import ( "github.com/prebid/prebid-server/analytics/filesystem" "github.com/prebid/prebid-server/analytics/pubstack" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/privacy" ) // Modules that need to be logged to need to be initialized here -func NewPBSAnalytics(analytics *config.Analytics) analytics.PBSAnalyticsModule { +func New(analytics *config.Analytics) analytics.Runner { modules := make(enabledAnalytics, 0) if len(analytics.File.Filename) > 0 { if mod, err := filesystem.NewFileLogger(analytics.File.Filename); err == nil { - modules = append(modules, mod) + modules["filelogger"] = mod } else { glog.Fatalf("Could not initialize FileLogger for file %v :%v", analytics.File.Filename, err) } @@ -32,7 +33,7 @@ func NewPBSAnalytics(analytics *config.Analytics) analytics.PBSAnalyticsModule { analytics.Pubstack.Buffers.Timeout, clock.New()) if err == nil { - modules = append(modules, pubstackModule) + modules["pubstack"] = pubstackModule } else { glog.Errorf("Could not initialize PubstackModule: %v", err) } @@ -41,17 +42,23 @@ func NewPBSAnalytics(analytics *config.Analytics) analytics.PBSAnalyticsModule { } // Collection of all the correctly configured analytics modules - implements the PBSAnalyticsModule interface -type enabledAnalytics []analytics.PBSAnalyticsModule +type enabledAnalytics map[string]analytics.Module -func (ea enabledAnalytics) LogAuctionObject(ao *analytics.AuctionObject) { - for _, module := range ea { - module.LogAuctionObject(ao) +func (ea enabledAnalytics) LogAuctionObject(ao *analytics.AuctionObject, ac privacy.ActivityControl) { + for name, module := range ea { + component := privacy.Component{Type: privacy.ComponentTypeAnalytics, Name: name} + if ac.Allow(privacy.ActivityReportAnalytics, component, privacy.ActivityRequest{}) { + module.LogAuctionObject(ao) + } } } -func (ea enabledAnalytics) LogVideoObject(vo *analytics.VideoObject) { - for _, module := range ea { - module.LogVideoObject(vo) +func (ea enabledAnalytics) LogVideoObject(vo *analytics.VideoObject, ac privacy.ActivityControl) { + for name, module := range ea { + component := privacy.Component{Type: privacy.ComponentTypeAnalytics, Name: name} + if ac.Allow(privacy.ActivityReportAnalytics, component, privacy.ActivityRequest{}) { + module.LogVideoObject(vo) + } } } @@ -67,14 +74,20 @@ func (ea enabledAnalytics) LogSetUIDObject(so *analytics.SetUIDObject) { } } -func (ea enabledAnalytics) LogAmpObject(ao *analytics.AmpObject) { - for _, module := range ea { - module.LogAmpObject(ao) +func (ea enabledAnalytics) LogAmpObject(ao *analytics.AmpObject, ac privacy.ActivityControl) { + for name, module := range ea { + component := privacy.Component{Type: privacy.ComponentTypeAnalytics, Name: name} + if ac.Allow(privacy.ActivityReportAnalytics, component, privacy.ActivityRequest{}) { + module.LogAmpObject(ao) + } } } -func (ea enabledAnalytics) LogNotificationEventObject(ne *analytics.NotificationEvent) { - for _, module := range ea { - module.LogNotificationEventObject(ne) +func (ea enabledAnalytics) LogNotificationEventObject(ne *analytics.NotificationEvent, ac privacy.ActivityControl) { + for name, module := range ea { + component := privacy.Component{Type: privacy.ComponentTypeAnalytics, Name: name} + if ac.Allow(privacy.ActivityReportAnalytics, component, privacy.ActivityRequest{}) { + module.LogNotificationEventObject(ne) + } } } diff --git a/analytics/config/config_test.go b/analytics/build/build_test.go similarity index 50% rename from analytics/config/config_test.go rename to analytics/build/build_test.go index c0ad9c26a16..dbd129a7afd 100644 --- a/analytics/config/config_test.go +++ b/analytics/build/build_test.go @@ -1,15 +1,16 @@ -package config +package build import ( + "github.com/prebid/prebid-server/analytics" "net/http" "os" "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/stretchr/testify/assert" - - "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/util/ptrutil" + "github.com/stretchr/testify/assert" ) const TEST_DIR string = "testFiles" @@ -21,7 +22,7 @@ func TestSampleModule(t *testing.T) { Status: http.StatusOK, Errors: nil, Response: &openrtb2.BidResponse{}, - }) + }, privacy.ActivityControl{}) if count != 1 { t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") } @@ -42,17 +43,17 @@ func TestSampleModule(t *testing.T) { t.Errorf("PBSAnalyticsModule failed at LogCookieSyncObject") } - am.LogAmpObject(&analytics.AmpObject{}) + am.LogAmpObject(&analytics.AmpObject{}, privacy.ActivityControl{}) if count != 4 { t.Errorf("PBSAnalyticsModule failed at LogAmpObject") } - am.LogVideoObject(&analytics.VideoObject{}) + am.LogVideoObject(&analytics.VideoObject{}, privacy.ActivityControl{}) if count != 5 { t.Errorf("PBSAnalyticsModule failed at LogVideoObject") } - am.LogNotificationEventObject(&analytics.NotificationEvent{}) + am.LogNotificationEventObject(&analytics.NotificationEvent{}, privacy.ActivityControl{}) if count != 6 { t.Errorf("PBSAnalyticsModule failed at LogNotificationEventObject") } @@ -74,14 +75,14 @@ func (m *sampleModule) LogAmpObject(ao *analytics.AmpObject) { *m.count++ } func (m *sampleModule) LogNotificationEventObject(ne *analytics.NotificationEvent) { *m.count++ } -func initAnalytics(count *int) analytics.PBSAnalyticsModule { +func initAnalytics(count *int) analytics.Runner { modules := make(enabledAnalytics, 0) - modules = append(modules, &sampleModule{count}) + modules["sampleModule"] = &sampleModule{count} return &modules } func TestNewPBSAnalytics(t *testing.T) { - pbsAnalytics := NewPBSAnalytics(&config.Analytics{}) + pbsAnalytics := New(&config.Analytics{}) instance := pbsAnalytics.(enabledAnalytics) assert.Equal(t, len(instance), 0) @@ -94,7 +95,7 @@ func TestNewPBSAnalytics_FileLogger(t *testing.T) { } } defer os.RemoveAll(TEST_DIR) - mod := NewPBSAnalytics(&config.Analytics{File: config.FileLogs{Filename: TEST_DIR + "/test"}}) + mod := New(&config.Analytics{File: config.FileLogs{Filename: TEST_DIR + "/test"}}) switch modType := mod.(type) { case enabledAnalytics: if len(enabledAnalytics(modType)) != 1 { @@ -104,7 +105,7 @@ func TestNewPBSAnalytics_FileLogger(t *testing.T) { t.Fatalf("Failed to initialize analytics module") } - pbsAnalytics := NewPBSAnalytics(&config.Analytics{File: config.FileLogs{Filename: TEST_DIR + "/test"}}) + pbsAnalytics := New(&config.Analytics{File: config.FileLogs{Filename: TEST_DIR + "/test"}}) instance := pbsAnalytics.(enabledAnalytics) assert.Equal(t, len(instance), 1) @@ -112,7 +113,7 @@ func TestNewPBSAnalytics_FileLogger(t *testing.T) { func TestNewPBSAnalytics_Pubstack(t *testing.T) { - pbsAnalyticsWithoutError := NewPBSAnalytics(&config.Analytics{ + pbsAnalyticsWithoutError := New(&config.Analytics{ Pubstack: config.Pubstack{ Enabled: true, ScopeId: "scopeId", @@ -129,7 +130,7 @@ func TestNewPBSAnalytics_Pubstack(t *testing.T) { assert.Equal(t, len(instanceWithoutError), 1) - pbsAnalyticsWithError := NewPBSAnalytics(&config.Analytics{ + pbsAnalyticsWithError := New(&config.Analytics{ Pubstack: config.Pubstack{ Enabled: true, }, @@ -137,3 +138,88 @@ func TestNewPBSAnalytics_Pubstack(t *testing.T) { instanceWithError := pbsAnalyticsWithError.(enabledAnalytics) assert.Equal(t, len(instanceWithError), 0) } + +func TestSampleModuleActivitiesAllowed(t *testing.T) { + var count int + am := initAnalytics(&count) + + acAllowed := privacy.NewActivityControl(getDefaultActivityConfig("sampleModule", true)) + + ao := &analytics.AuctionObject{ + Status: http.StatusOK, + Errors: nil, + Response: &openrtb2.BidResponse{}, + } + + am.LogAuctionObject(ao, acAllowed) + if count != 1 { + t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") + } + + am.LogAmpObject(&analytics.AmpObject{}, acAllowed) + if count != 2 { + t.Errorf("PBSAnalyticsModule failed at LogAmpObject") + } + + am.LogVideoObject(&analytics.VideoObject{}, acAllowed) + if count != 3 { + t.Errorf("PBSAnalyticsModule failed at LogVideoObject") + } + + am.LogNotificationEventObject(&analytics.NotificationEvent{}, acAllowed) + if count != 4 { + t.Errorf("PBSAnalyticsModule failed at LogNotificationEventObject") + } +} + +func TestSampleModuleActivitiesDenied(t *testing.T) { + var count int + am := initAnalytics(&count) + + acDenied := privacy.NewActivityControl(getDefaultActivityConfig("sampleModule", false)) + + ao := &analytics.AuctionObject{ + Status: http.StatusOK, + Errors: nil, + Response: &openrtb2.BidResponse{}, + } + + am.LogAuctionObject(ao, acDenied) + if count != 0 { + t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") + } + + am.LogAmpObject(&analytics.AmpObject{}, acDenied) + if count != 0 { + t.Errorf("PBSAnalyticsModule failed at LogAmpObject") + } + + am.LogVideoObject(&analytics.VideoObject{}, acDenied) + if count != 0 { + t.Errorf("PBSAnalyticsModule failed at LogVideoObject") + } + + am.LogNotificationEventObject(&analytics.NotificationEvent{}, acDenied) + if count != 0 { + t.Errorf("PBSAnalyticsModule failed at LogNotificationEventObject") + } +} + +func getDefaultActivityConfig(componentName string, allow bool) *config.AccountPrivacy { + return &config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + ReportAnalytics: config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: allow, + Condition: config.ActivityCondition{ + ComponentName: []string{componentName}, + ComponentType: []string{"analytics"}, + }, + }, + }, + }, + }, + } +} diff --git a/analytics/core.go b/analytics/core.go index eca93741bd2..0279ae83868 100644 --- a/analytics/core.go +++ b/analytics/core.go @@ -9,10 +9,10 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -// PBSAnalyticsModule must be implemented by analytics modules to extract the required information and logging +// Module must be implemented by analytics modules to extract the required information and logging // activities. Do not use marshal the parameter objects directly as they can change over time. Use a separate // model for each analytics module and transform as appropriate. -type PBSAnalyticsModule interface { +type Module interface { LogAuctionObject(*AuctionObject) LogVideoObject(*VideoObject) LogCookieSyncObject(*CookieSyncObject) diff --git a/analytics/filesystem/file_module.go b/analytics/filesystem/file_module.go index 9a357529c3a..f055e4470b1 100644 --- a/analytics/filesystem/file_module.go +++ b/analytics/filesystem/file_module.go @@ -86,7 +86,7 @@ func (f *FileLogger) LogNotificationEventObject(ne *analytics.NotificationEvent) } // Method to initialize the analytic module -func NewFileLogger(filename string) (analytics.PBSAnalyticsModule, error) { +func NewFileLogger(filename string) (analytics.Module, error) { options := glog.LogOptions{ File: filename, Flag: glog.LstdFlags, diff --git a/analytics/pubstack/pubstack_module.go b/analytics/pubstack/pubstack_module.go index 987c935f884..f2d8726d356 100644 --- a/analytics/pubstack/pubstack_module.go +++ b/analytics/pubstack/pubstack_module.go @@ -50,7 +50,7 @@ type PubstackModule struct { clock clock.Clock } -func NewModule(client *http.Client, scope, endpoint, configRefreshDelay string, maxEventCount int, maxByteSize, maxTime string, clock clock.Clock) (analytics.PBSAnalyticsModule, error) { +func NewModule(client *http.Client, scope, endpoint, configRefreshDelay string, maxEventCount int, maxByteSize, maxTime string, clock clock.Clock) (analytics.Module, error) { configUpdateTask, err := NewConfigUpdateHttpTask( client, scope, @@ -63,7 +63,7 @@ func NewModule(client *http.Client, scope, endpoint, configRefreshDelay string, return NewModuleWithConfigTask(client, scope, endpoint, maxEventCount, maxByteSize, maxTime, configUpdateTask, clock) } -func NewModuleWithConfigTask(client *http.Client, scope, endpoint string, maxEventCount int, maxByteSize, maxTime string, configTask ConfigUpdateTask, clock clock.Clock) (analytics.PBSAnalyticsModule, error) { +func NewModuleWithConfigTask(client *http.Client, scope, endpoint string, maxEventCount int, maxByteSize, maxTime string, configTask ConfigUpdateTask, clock clock.Clock) (analytics.Module, error) { glog.Infof("[pubstack] Initializing module scope=%s endpoint=%s\n", scope, endpoint) // parse args diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index 504a1cfe17e..23e110df9c1 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -50,47 +50,47 @@ func TestNewModuleSuccess(t *testing.T) { tests := []struct { description string feature string - logObject func(analytics.PBSAnalyticsModule) + logObject func(analytics.Module) }{ { description: "auction events are only published when logging an auction object with auction feature on", feature: auction, - logObject: func(module analytics.PBSAnalyticsModule) { + logObject: func(module analytics.Module) { module.LogAuctionObject(&analytics.AuctionObject{Status: http.StatusOK}) }, }, { description: "AMP events are only published when logging an AMP object with AMP feature on", feature: amp, - logObject: func(module analytics.PBSAnalyticsModule) { + logObject: func(module analytics.Module) { module.LogAmpObject(&analytics.AmpObject{Status: http.StatusOK}) }, }, { description: "video events are only published when logging a video object with video feature on", feature: video, - logObject: func(module analytics.PBSAnalyticsModule) { + logObject: func(module analytics.Module) { module.LogVideoObject(&analytics.VideoObject{Status: http.StatusOK}) }, }, { description: "cookie events are only published when logging a cookie object with cookie feature on", feature: cookieSync, - logObject: func(module analytics.PBSAnalyticsModule) { + logObject: func(module analytics.Module) { module.LogCookieSyncObject(&analytics.CookieSyncObject{Status: http.StatusOK}) }, }, { description: "setUID events are only published when logging a setUID object with setUID feature on", feature: setUID, - logObject: func(module analytics.PBSAnalyticsModule) { + logObject: func(module analytics.Module) { module.LogSetUIDObject(&analytics.SetUIDObject{Status: http.StatusOK}) }, }, { description: "Ignore excluded fields from marshal", feature: auction, - logObject: func(module analytics.PBSAnalyticsModule) { + logObject: func(module analytics.Module) { module.LogAuctionObject(&analytics.AuctionObject{ RequestWrapper: &openrtb_ext.RequestWrapper{}, SeatNonBid: []openrtb_ext.SeatNonBid{ diff --git a/analytics/runner.go b/analytics/runner.go new file mode 100644 index 00000000000..08f67561f8a --- /dev/null +++ b/analytics/runner.go @@ -0,0 +1,14 @@ +package analytics + +import ( + "github.com/prebid/prebid-server/privacy" +) + +type Runner interface { + LogAuctionObject(*AuctionObject, privacy.ActivityControl) + LogVideoObject(*VideoObject, privacy.ActivityControl) + LogCookieSyncObject(*CookieSyncObject) + LogSetUIDObject(*SetUIDObject) + LogAmpObject(*AmpObject, privacy.ActivityControl) + LogNotificationEventObject(*NotificationEvent, privacy.ActivityControl) +} diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index bbee3847014..24530efa56a 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -50,7 +50,7 @@ func NewCookieSyncEndpoint( gdprPermsBuilder gdpr.PermissionsBuilder, tcf2CfgBuilder gdpr.TCF2ConfigBuilder, metrics metrics.MetricsEngine, - pbsAnalytics analytics.PBSAnalyticsModule, + analyticsRunner analytics.Runner, accountsFetcher stored_requests.AccountFetcher, bidders map[string]openrtb_ext.BidderName) HTTPRouterHandler { @@ -70,7 +70,7 @@ func NewCookieSyncEndpoint( bidderHashSet: bidderHashSet, }, metrics: metrics, - pbsAnalytics: pbsAnalytics, + pbsAnalytics: analyticsRunner, accountsFetcher: accountsFetcher, } } @@ -80,7 +80,7 @@ type cookieSyncEndpoint struct { config *config.Configuration privacyConfig usersyncPrivacyConfig metrics metrics.MetricsEngine - pbsAnalytics analytics.PBSAnalyticsModule + pbsAnalytics analytics.Runner accountsFetcher stored_requests.AccountFetcher } diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 80de2b5ff50..45f7a75da0c 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -42,7 +42,7 @@ func TestNewCookieSyncEndpoint(t *testing.T) { configGDPR = config.GDPR{HostVendorID: 42} configCCPAEnforce = true metrics = metrics.MetricsEngineMock{} - analytics = MockAnalytics{} + analytics = MockAnalyticsRunner{} fetcher = FakeAccountsFetcher{} bidders = map[string]openrtb_ext.BidderName{"bidderA": openrtb_ext.BidderName("bidderA"), "bidderB": openrtb_ext.BidderName("bidderB")} ) @@ -114,7 +114,7 @@ func TestCookieSyncHandle(t *testing.T) { expectedStatusCode int expectedBody string setMetricsExpectations func(*metrics.MetricsEngineMock) - setAnalyticsExpectations func(*MockAnalytics) + setAnalyticsExpectations func(*MockAnalyticsRunner) }{ { description: "Request With Cookie", @@ -133,7 +133,7 @@ func TestCookieSyncHandle(t *testing.T) { m.On("RecordCookieSync", metrics.CookieSyncOK).Once() m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncOK).Once() }, - setAnalyticsExpectations: func(a *MockAnalytics) { + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { expected := analytics.CookieSyncObject{ Status: 200, Errors: nil, @@ -165,7 +165,7 @@ func TestCookieSyncHandle(t *testing.T) { m.On("RecordCookieSync", metrics.CookieSyncOK).Once() m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncOK).Once() }, - setAnalyticsExpectations: func(a *MockAnalytics) { + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { expected := analytics.CookieSyncObject{ Status: 200, Errors: nil, @@ -194,7 +194,7 @@ func TestCookieSyncHandle(t *testing.T) { setMetricsExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordCookieSync", metrics.CookieSyncBadRequest).Once() }, - setAnalyticsExpectations: func(a *MockAnalytics) { + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { expected := analytics.CookieSyncObject{ Status: 400, Errors: []error{errors.New("JSON parsing failed: invalid character 'm' looking for beginning of value")}, @@ -217,7 +217,7 @@ func TestCookieSyncHandle(t *testing.T) { setMetricsExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordCookieSync", metrics.CookieSyncOptOut).Once() }, - setAnalyticsExpectations: func(a *MockAnalytics) { + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { expected := analytics.CookieSyncObject{ Status: 401, Errors: []error{errors.New("User has opted out")}, @@ -240,7 +240,7 @@ func TestCookieSyncHandle(t *testing.T) { setMetricsExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordCookieSync", metrics.CookieSyncGDPRHostCookieBlocked).Once() }, - setAnalyticsExpectations: func(a *MockAnalytics) { + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { expected := analytics.CookieSyncObject{ Status: 200, Errors: nil, @@ -255,7 +255,7 @@ func TestCookieSyncHandle(t *testing.T) { mockMetrics := metrics.MetricsEngineMock{} test.setMetricsExpectations(&mockMetrics) - mockAnalytics := MockAnalytics{} + mockAnalytics := MockAnalyticsRunner{} test.setAnalyticsExpectations(&mockAnalytics) fakeAccountFetcher := FakeAccountsFetcher{} @@ -1248,7 +1248,7 @@ func TestSetCooperativeSync(t *testing.T) { func TestWriteParseRequestErrorMetrics(t *testing.T) { err := errors.New("anyError") - mockAnalytics := MockAnalytics{} + mockAnalytics := MockAnalyticsRunner{} mockAnalytics.On("LogCookieSyncObject", mock.Anything) writer := httptest.NewRecorder() @@ -1471,7 +1471,7 @@ func TestParseBidderFilter(t *testing.T) { func TestCookieSyncHandleError(t *testing.T) { err := errors.New("anyError") - mockAnalytics := MockAnalytics{} + mockAnalytics := MockAnalyticsRunner{} mockAnalytics.On("LogCookieSyncObject", mock.Anything) writer := httptest.NewRecorder() @@ -1664,7 +1664,7 @@ func TestCookieSyncHandleResponse(t *testing.T) { } for _, test := range testCases { - mockAnalytics := MockAnalytics{} + mockAnalytics := MockAnalyticsRunner{} mockAnalytics.On("LogCookieSyncObject", &test.expectedAnalytics).Once() cookie := usersync.NewCookie() @@ -1996,32 +1996,32 @@ func (m *MockSyncer) GetSync(syncTypes []usersync.SyncType, privacyMacros macros return args.Get(0).(usersync.Sync), args.Error(1) } -type MockAnalytics struct { +type MockAnalyticsRunner struct { mock.Mock } -func (m *MockAnalytics) LogAuctionObject(obj *analytics.AuctionObject) { - m.Called(obj) +func (m *MockAnalyticsRunner) LogAuctionObject(obj *analytics.AuctionObject, ac privacy.ActivityControl) { + m.Called(obj, ac) } -func (m *MockAnalytics) LogVideoObject(obj *analytics.VideoObject) { - m.Called(obj) +func (m *MockAnalyticsRunner) LogVideoObject(obj *analytics.VideoObject, ac privacy.ActivityControl) { + m.Called(obj, ac) } -func (m *MockAnalytics) LogCookieSyncObject(obj *analytics.CookieSyncObject) { +func (m *MockAnalyticsRunner) LogCookieSyncObject(obj *analytics.CookieSyncObject) { m.Called(obj) } -func (m *MockAnalytics) LogSetUIDObject(obj *analytics.SetUIDObject) { +func (m *MockAnalyticsRunner) LogSetUIDObject(obj *analytics.SetUIDObject) { m.Called(obj) } -func (m *MockAnalytics) LogAmpObject(obj *analytics.AmpObject) { - m.Called(obj) +func (m *MockAnalyticsRunner) LogAmpObject(obj *analytics.AmpObject, ac privacy.ActivityControl) { + m.Called(obj, ac) } -func (m *MockAnalytics) LogNotificationEventObject(obj *analytics.NotificationEvent) { - m.Called(obj) +func (m *MockAnalyticsRunner) LogNotificationEventObject(obj *analytics.NotificationEvent, ac privacy.ActivityControl) { + m.Called(obj, ac) } type MockGDPRPerms struct { diff --git a/endpoints/events/event.go b/endpoints/events/event.go index 7f923eda09d..d5cf89b2ef5 100644 --- a/endpoints/events/event.go +++ b/endpoints/events/event.go @@ -16,6 +16,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/util/httputil" ) @@ -40,13 +41,13 @@ const integrationParamMaxLength = 64 type eventEndpoint struct { Accounts stored_requests.AccountFetcher - Analytics analytics.PBSAnalyticsModule + Analytics analytics.Runner Cfg *config.Configuration TrackingPixel *httputil.Pixel MetricsEngine metrics.MetricsEngine } -func NewEventEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, analytics analytics.PBSAnalyticsModule, me metrics.MetricsEngine) httprouter.Handle { +func NewEventEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, analytics analytics.Runner, me metrics.MetricsEngine) httprouter.Handle { ee := &eventEndpoint{ Accounts: accounts, Analytics: analytics, @@ -114,11 +115,13 @@ func (e *eventEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprou return } + activities := privacy.NewActivityControl(&account.Privacy) + // handle notification event e.Analytics.LogNotificationEventObject(&analytics.NotificationEvent{ Request: eventRequest, Account: account, - }) + }, activities) // Add tracking pixel if format == image if eventRequest.Format == analytics.Image { diff --git a/endpoints/events/event_test.go b/endpoints/events/event_test.go index 7f129634221..d721187b6ff 100644 --- a/endpoints/events/event_test.go +++ b/endpoints/events/event_test.go @@ -16,6 +16,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/stored_requests" "github.com/stretchr/testify/assert" ) @@ -26,13 +27,13 @@ type eventsMockAnalyticsModule struct { Invoked bool } -func (e *eventsMockAnalyticsModule) LogAuctionObject(ao *analytics.AuctionObject) { +func (e *eventsMockAnalyticsModule) LogAuctionObject(ao *analytics.AuctionObject, _ privacy.ActivityControl) { if e.Fail { panic(e.Error) } } -func (e *eventsMockAnalyticsModule) LogVideoObject(vo *analytics.VideoObject) { +func (e *eventsMockAnalyticsModule) LogVideoObject(vo *analytics.VideoObject, _ privacy.ActivityControl) { if e.Fail { panic(e.Error) } @@ -50,13 +51,13 @@ func (e *eventsMockAnalyticsModule) LogSetUIDObject(so *analytics.SetUIDObject) } } -func (e *eventsMockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject) { +func (e *eventsMockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject, _ privacy.ActivityControl) { if e.Fail { panic(e.Error) } } -func (e *eventsMockAnalyticsModule) LogNotificationEventObject(ne *analytics.NotificationEvent) { +func (e *eventsMockAnalyticsModule) LogNotificationEventObject(ne *analytics.NotificationEvent, _ privacy.ActivityControl) { if e.Fail { panic(e.Error) } diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index aa80d0756c3..76762d8b9ea 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -11,8 +11,6 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/privacy" - "github.com/buger/jsonparser" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -33,6 +31,7 @@ import ( "github.com/prebid/prebid-server/hooks" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/stored_responses" @@ -64,7 +63,7 @@ func NewAmpEndpoint( accounts stored_requests.AccountFetcher, cfg *config.Configuration, metricsEngine metrics.MetricsEngine, - pbsAnalytics analytics.PBSAnalyticsModule, + analyticsRunner analytics.Runner, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, @@ -93,7 +92,7 @@ func NewAmpEndpoint( accounts, cfg, metricsEngine, - pbsAnalytics, + analyticsRunner, disabledBidders, defRequest, defReqJSON, @@ -134,11 +133,12 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h CookieFlag: metrics.CookieFlagUnknown, RequestStatus: metrics.RequestStatusOK, } + activityControl := privacy.ActivityControl{} defer func() { deps.metricsEngine.RecordRequest(labels) deps.metricsEngine.RecordRequestTime(labels, time.Since(start)) - deps.analytics.LogAmpObject(&ao) + deps.analytics.LogAmpObject(&ao, activityControl) }() // Add AMP headers @@ -230,7 +230,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR) - activityControl := privacy.NewActivityControl(&account.Privacy) + activityControl = privacy.NewActivityControl(&account.Privacy) secGPC := r.Header.Get("Sec-GPC") diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 89d9c472925..3c0feb4f7ab 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -20,7 +20,7 @@ import ( "github.com/prebid/prebid-server/amp" "github.com/prebid/prebid-server/analytics" - analyticsConf "github.com/prebid/prebid-server/analytics/config" + analyticsBuild "github.com/prebid/prebid-server/analytics/build" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" @@ -30,6 +30,7 @@ import ( "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" ) @@ -201,7 +202,7 @@ func TestAMPPageInfo(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -305,7 +306,7 @@ func TestGDPRConsent(t *testing.T) { GDPR: config.GDPR{Enabled: true}, }, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -729,7 +730,7 @@ func TestCCPAConsent(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -843,7 +844,7 @@ func TestConsentWarnings(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -942,7 +943,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { GDPR: config.GDPR{Enabled: true}, }, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -997,7 +998,7 @@ func TestAMPSiteExt(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), nil, nil, openrtb_ext.BuildBidderMap(), @@ -1040,7 +1041,7 @@ func TestAmpBadRequests(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -1074,7 +1075,7 @@ func TestAmpDebug(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -1210,7 +1211,7 @@ func TestQueryParamOverrides(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -1368,7 +1369,7 @@ func (s formatOverrideSpec) execute(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -1631,25 +1632,25 @@ type mockLogger struct { auctionObject *analytics.AuctionObject } -func newMockLogger(ao *analytics.AmpObject, aucObj *analytics.AuctionObject) analytics.PBSAnalyticsModule { +func newMockLogger(ao *analytics.AmpObject, aucObj *analytics.AuctionObject) analytics.Runner { return &mockLogger{ ampObject: ao, auctionObject: aucObj, } } -func (logger mockLogger) LogAuctionObject(ao *analytics.AuctionObject) { +func (logger mockLogger) LogAuctionObject(ao *analytics.AuctionObject, _ privacy.ActivityControl) { *logger.auctionObject = *ao } -func (logger mockLogger) LogVideoObject(vo *analytics.VideoObject) { +func (logger mockLogger) LogVideoObject(vo *analytics.VideoObject, _ privacy.ActivityControl) { } func (logger mockLogger) LogCookieSyncObject(cookieObject *analytics.CookieSyncObject) { } func (logger mockLogger) LogSetUIDObject(uuidObj *analytics.SetUIDObject) { } -func (logger mockLogger) LogNotificationEventObject(uuidObj *analytics.NotificationEvent) { +func (logger mockLogger) LogNotificationEventObject(uuidObj *analytics.NotificationEvent, _ privacy.ActivityControl) { } -func (logger mockLogger) LogAmpObject(ao *analytics.AmpObject) { +func (logger mockLogger) LogAmpObject(ao *analytics.AmpObject, _ privacy.ActivityControl) { *logger.ampObject = *ao } @@ -1962,7 +1963,7 @@ func TestAmpAuctionResponseHeaders(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -1998,7 +1999,7 @@ func TestRequestWithTargeting(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), nil, nil, openrtb_ext.BuildBidderMap(), diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index b625c0ff54c..4fce4c15786 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -14,8 +14,6 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/privacy" - "github.com/buger/jsonparser" "github.com/gofrs/uuid" "github.com/golang/glog" @@ -30,6 +28,7 @@ import ( "github.com/prebid/prebid-server/bidadjustment" "github.com/prebid/prebid-server/hooks" "github.com/prebid/prebid-server/ortb" + "github.com/prebid/prebid-server/privacy" "golang.org/x/net/publicsuffix" jsonpatch "gopkg.in/evanphx/json-patch.v4" @@ -89,7 +88,7 @@ func NewEndpoint( accounts stored_requests.AccountFetcher, cfg *config.Configuration, metricsEngine metrics.MetricsEngine, - pbsAnalytics analytics.PBSAnalyticsModule, + analyticsRunner analytics.Runner, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, @@ -117,7 +116,7 @@ func NewEndpoint( accounts, cfg, metricsEngine, - pbsAnalytics, + analyticsRunner, disabledBidders, defRequest, defReqJSON, @@ -139,7 +138,7 @@ type endpointDeps struct { accounts stored_requests.AccountFetcher cfg *config.Configuration metricsEngine metrics.MetricsEngine - analytics analytics.PBSAnalyticsModule + analytics analytics.Runner disabledBidders map[string]string defaultRequest bool defReqJSON []byte @@ -176,10 +175,12 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http CookieFlag: metrics.CookieFlagUnknown, RequestStatus: metrics.RequestStatusOK, } + + activityControl := privacy.ActivityControl{} defer func() { deps.metricsEngine.RecordRequest(labels) deps.metricsEngine.RecordRequestTime(labels, time.Since(start)) - deps.analytics.LogAuctionObject(&ao) + deps.analytics.LogAuctionObject(&ao, activityControl) }() w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver)) @@ -197,7 +198,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR) - activityControl := privacy.NewActivityControl(&account.Privacy) + activityControl = privacy.NewActivityControl(&account.Privacy) ctx := context.Background() diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index ee74548ea47..f3e82dca099 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - analyticsConf "github.com/prebid/prebid-server/analytics/config" + analyticsBuild "github.com/prebid/prebid-server/analytics/build" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/exchange" @@ -105,7 +105,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, nilMetrics, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, nil, diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index b5631c93d3d..55bf68ff730 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -23,7 +23,7 @@ import ( nativeRequests "github.com/prebid/openrtb/v19/native1/request" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/analytics" - analyticsConf "github.com/prebid/prebid-server/analytics/config" + analyticsBuild "github.com/prebid/prebid-server/analytics/build" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" @@ -447,7 +447,7 @@ func TestExplicitUserId(t *testing.T) { empty_fetcher.EmptyFetcher{}, cfg, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -505,7 +505,7 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), disabledBidders, aliasJSON, bidderMap, @@ -560,7 +560,7 @@ func TestNilExchange(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, @@ -585,7 +585,7 @@ func TestNilValidator(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -611,7 +611,7 @@ func TestExchangeError(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -738,7 +738,7 @@ func TestImplicitIPsEndToEnd(t *testing.T) { empty_fetcher.EmptyFetcher{}, cfg, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -938,7 +938,7 @@ func TestImplicitDNTEndToEnd(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -1175,7 +1175,7 @@ func TestStoredRequests(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -1624,7 +1624,7 @@ func TestValidateRequest(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2403,7 +2403,7 @@ func TestSetIntegrationType(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2469,7 +2469,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2573,7 +2573,7 @@ func TestOversizedRequest(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody) - 1)}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2612,7 +2612,7 @@ func TestRequestSizeEdgeCase(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody))}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2649,7 +2649,7 @@ func TestNoEncoding(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -2734,7 +2734,7 @@ func TestContentType(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -2951,7 +2951,7 @@ func TestValidateImpExt(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(8096)}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{"disabledbidder": "The bidder 'disabledbidder' has been disabled."}, false, []byte{}, @@ -3006,7 +3006,7 @@ func TestCurrencyTrunc(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -3054,7 +3054,7 @@ func TestCCPAInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -3106,7 +3106,7 @@ func TestNoSaleInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -3161,7 +3161,7 @@ func TestValidateSourceTID(t *testing.T) { empty_fetcher.EmptyFetcher{}, cfg, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -3206,7 +3206,7 @@ func TestSChainInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -3774,7 +3774,7 @@ func TestEidPermissionsInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -4028,7 +4028,7 @@ func TestIOS14EndToEnd(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -4091,7 +4091,7 @@ func TestAuctionWarnings(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -4137,7 +4137,7 @@ func TestParseRequestParseImpInfoError(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody))}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -4217,7 +4217,7 @@ func TestParseGzipedRequest(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(50), Compression: config.Compression{Request: config.CompressionInfo{GZIP: false}}}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -4716,7 +4716,7 @@ func TestAuctionResponseHeaders(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), @@ -4817,7 +4817,7 @@ func TestParseRequestMergeBidderParams(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -4920,7 +4920,7 @@ func TestParseRequestStoredResponses(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -5018,7 +5018,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -5054,7 +5054,7 @@ func TestValidateStoredResp(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -5867,7 +5867,7 @@ func TestParseRequestMultiBid(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index 2d22f1e4ebe..365f480eb4c 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -20,7 +20,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb3" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/analytics" - analyticsConf "github.com/prebid/prebid-server/analytics/config" + analyticsBuild "github.com/prebid/prebid-server/analytics/build" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" @@ -1275,7 +1275,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han planBuilder = hooks.EmptyPlanBuilder{} } - var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.PBSAnalyticsModule, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder, *exchange.TmaxAdjustmentsPreprocessed) (httprouter.Handle, error) + var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.Runner, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder, *exchange.TmaxAdjustmentsPreprocessed) (httprouter.Handle, error) switch test.endpointType { case AMP_ENDPOINT: @@ -1292,7 +1292,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han accountFetcher, cfg, met, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), disabledBidders, []byte(test.Config.AliasJSON), bidderMap, diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 7b2a7a5295a..fc6d2a91a62 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -52,7 +52,7 @@ func NewVideoEndpoint( accounts stored_requests.AccountFetcher, cfg *config.Configuration, met metrics.MetricsEngine, - pbsAnalytics analytics.PBSAnalyticsModule, + analyticsRunner analytics.Runner, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, @@ -82,7 +82,7 @@ func NewVideoEndpoint( accounts, cfg, met, - pbsAnalytics, + analyticsRunner, disabledBidders, defRequest, defReqJSON, @@ -149,6 +149,8 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } debugLog.DebugEnabledOrOverridden = debugLog.Enabled || debugLog.DebugOverride + activityControl := privacy.ActivityControl{} + defer func() { if len(debugLog.CacheKey) > 0 && vo.VideoResponse == nil { err := debugLog.PutDebugLogError(deps.cache, deps.cfg.CacheURL.ExpectedTimeMillis, vo.Errors) @@ -158,7 +160,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } deps.metricsEngine.RecordRequest(labels) deps.metricsEngine.RecordRequestTime(labels, time.Since(start)) - deps.analytics.LogVideoObject(&vo) + deps.analytics.LogVideoObject(&vo, activityControl) }() w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver)) @@ -303,7 +305,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re return } - activityControl := privacy.NewActivityControl(&account.Privacy) + activityControl = privacy.NewActivityControl(&account.Privacy) secGPC := r.Header.Get("Sec-GPC") auctionRequest := &exchange.AuctionRequest{ diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index f3135ff48db..181a68929ed 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -12,7 +12,7 @@ import ( "testing" "github.com/prebid/prebid-server/analytics" - analyticsConf "github.com/prebid/prebid-server/analytics/config" + analyticsBuild "github.com/prebid/prebid-server/analytics/build" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" @@ -21,6 +21,7 @@ import ( metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" + "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/util/ptrutil" @@ -1235,11 +1236,11 @@ type mockAnalyticsModule struct { videoObjects []*analytics.VideoObject } -func (m *mockAnalyticsModule) LogAuctionObject(ao *analytics.AuctionObject) { +func (m *mockAnalyticsModule) LogAuctionObject(ao *analytics.AuctionObject, _ privacy.ActivityControl) { m.auctionObjects = append(m.auctionObjects, ao) } -func (m *mockAnalyticsModule) LogVideoObject(vo *analytics.VideoObject) { +func (m *mockAnalyticsModule) LogVideoObject(vo *analytics.VideoObject, _ privacy.ActivityControl) { m.videoObjects = append(m.videoObjects, vo) } @@ -1247,9 +1248,10 @@ func (m *mockAnalyticsModule) LogCookieSyncObject(cso *analytics.CookieSyncObjec func (m *mockAnalyticsModule) LogSetUIDObject(so *analytics.SetUIDObject) {} -func (m *mockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject) {} +func (m *mockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject, _ privacy.ActivityControl) {} -func (m *mockAnalyticsModule) LogNotificationEventObject(ne *analytics.NotificationEvent) {} +func (m *mockAnalyticsModule) LogNotificationEventObject(ne *analytics.NotificationEvent, _ privacy.ActivityControl) { +} func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { return &endpointDeps{ @@ -1261,7 +1263,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { &mockAccountFetcher{data: mockVideoAccountData}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -1285,7 +1287,7 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -1311,7 +1313,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, diff --git a/endpoints/setuid.go b/endpoints/setuid.go index 520a133b51e..1bf3b575273 100644 --- a/endpoints/setuid.go +++ b/endpoints/setuid.go @@ -36,7 +36,7 @@ const ( const uidCookieName = "uids" -func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, gdprPermsBuilder gdpr.PermissionsBuilder, tcf2CfgBuilder gdpr.TCF2ConfigBuilder, pbsanalytics analytics.PBSAnalyticsModule, accountsFetcher stored_requests.AccountFetcher, metricsEngine metrics.MetricsEngine) httprouter.Handle { +func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, gdprPermsBuilder gdpr.PermissionsBuilder, tcf2CfgBuilder gdpr.TCF2ConfigBuilder, analyticsRunner analytics.Runner, accountsFetcher stored_requests.AccountFetcher, metricsEngine metrics.MetricsEngine) httprouter.Handle { encoder := usersync.Base64Encoder{} decoder := usersync.Base64Decoder{} @@ -46,7 +46,7 @@ func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]use Errors: make([]error, 0), } - defer pbsanalytics.LogSetUIDObject(&so) + defer analyticsRunner.LogSetUIDObject(&so) cookie := usersync.ReadCookie(r, decoder, &cfg.HostCookie) if !cookie.AllowSyncs() { diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 18099193045..3636f7fbbdc 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -13,6 +13,7 @@ import ( "time" "github.com/prebid/prebid-server/analytics" + analyticsBuild "github.com/prebid/prebid-server/analytics/build" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/gdpr" @@ -22,7 +23,6 @@ import ( "github.com/prebid/prebid-server/usersync" "github.com/stretchr/testify/assert" - analyticsConf "github.com/prebid/prebid-server/analytics/config" metricsConf "github.com/prebid/prebid-server/metrics/config" ) @@ -328,7 +328,7 @@ func TestSetUIDEndpoint(t *testing.T) { }, } - analytics := analyticsConf.NewPBSAnalytics(&config.Analytics{}) + analytics := analyticsBuild.New(&config.Analytics{}) metrics := &metricsConf.NilMetricsEngine{} for _, test := range testCases { @@ -362,7 +362,7 @@ func TestSetUIDEndpoint(t *testing.T) { func TestSetUIDPriorityEjection(t *testing.T) { decoder := usersync.Base64Decoder{} - analytics := analyticsConf.NewPBSAnalytics(&config.Analytics{}) + analytics := analyticsBuild.New(&config.Analytics{}) syncersByBidder := map[string]string{ "pubmatic": "pubmatic", "syncer1": "syncer1", @@ -1093,7 +1093,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { cfgAccountRequired bool expectedResponseCode int expectedMetrics func(*metrics.MetricsEngineMock) - expectedAnalytics func(*MockAnalytics) + expectedAnalytics func(*MockAnalyticsRunner) }{ { description: "Success - Sync", @@ -1106,7 +1106,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { m.On("RecordSetUid", metrics.SetUidOK).Once() m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidOK).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 200, Bidder: "pubmatic", @@ -1128,7 +1128,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { m.On("RecordSetUid", metrics.SetUidOK).Once() m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidCleared).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 200, Bidder: "pubmatic", @@ -1149,7 +1149,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidOptOut).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 401, Bidder: "", @@ -1170,7 +1170,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidSyncerUnknown).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "", @@ -1191,7 +1191,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidBadRequest).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "pubmatic", @@ -1212,7 +1212,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidBadRequest).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "pubmatic", @@ -1233,7 +1233,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidGDPRHostCookieBlocked).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 451, Bidder: "pubmatic", @@ -1255,7 +1255,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidAccountInvalid).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "pubmatic", @@ -1277,7 +1277,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidAccountConfigMalformed).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "pubmatic", @@ -1299,7 +1299,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidBadRequest).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "pubmatic", @@ -1313,7 +1313,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { } for _, test := range testCases { - analyticsEngine := &MockAnalytics{} + analyticsEngine := &MockAnalyticsRunner{} test.expectedAnalytics(analyticsEngine) metricsEngine := &metrics.MetricsEngineMock{} @@ -1337,7 +1337,7 @@ func TestOptedOut(t *testing.T) { cookie.SetOptOut(true) addCookie(request, cookie) syncersBidderNameToKey := map[string]string{"pubmatic": "pubmatic"} - analytics := analyticsConf.NewPBSAnalytics(&config.Analytics{}) + analytics := analyticsBuild.New(&config.Analytics{}) metrics := &metricsConf.NilMetricsEngine{} response := doRequest(request, analytics, metrics, syncersBidderNameToKey, true, false, false, false, 0, nil) @@ -1525,7 +1525,7 @@ func makeRequest(uri string, existingSyncs map[string]string) *http.Request { return request } -func doRequest(req *http.Request, analytics analytics.PBSAnalyticsModule, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError, cfgAccountRequired bool, maxCookieSize int, priorityGroups [][]string) *httptest.ResponseRecorder { +func doRequest(req *http.Request, analytics analytics.Runner, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError, cfgAccountRequired bool, maxCookieSize int, priorityGroups [][]string) *httptest.ResponseRecorder { cfg := config.Configuration{ AccountRequired: cfgAccountRequired, AccountDefaults: config.Account{}, diff --git a/router/router.go b/router/router.go index f2b1f7e7447..29bd8382e78 100644 --- a/router/router.go +++ b/router/router.go @@ -10,7 +10,7 @@ import ( "strings" "time" - analyticsConf "github.com/prebid/prebid-server/analytics/config" + analyticsBuild "github.com/prebid/prebid-server/analytics/build" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/endpoints" @@ -191,7 +191,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R // todo(zachbadgett): better shutdown r.Shutdown = shutdown - pbsAnalytics := analyticsConf.NewPBSAnalytics(&cfg.Analytics) + analyticsRunner := analyticsBuild.New(&cfg.Analytics) paramsValidator, err := openrtb_ext.NewBidderParamsValidator(schemaDirectory) if err != nil { @@ -228,17 +228,17 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R macroReplacer := macros.NewStringIndexBasedReplacer() theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, cfg.BidderInfos, gdprPermsBuilder, rateConvertor, categoriesFetcher, adsCertSigner, macroReplacer) var uuidGenerator uuidutil.UUIDRandomGenerator - openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments) + openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments) if err != nil { glog.Fatalf("Failed to create the openrtb2 endpoint handler. %v", err) } - ampEndpoint, err := openrtb2.NewAmpEndpoint(uuidGenerator, theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments) + ampEndpoint, err := openrtb2.NewAmpEndpoint(uuidGenerator, theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments) if err != nil { glog.Fatalf("Failed to create the amp endpoint handler. %v", err) } - videoEndpoint, err := openrtb2.NewVideoEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, cacheClient, tmaxAdjustments) + videoEndpoint, err := openrtb2.NewVideoEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, cacheClient, tmaxAdjustments) if err != nil { glog.Fatalf("Failed to create the video endpoint handler. %v", err) } @@ -254,7 +254,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R r.GET("/info/bidders", infoEndpoints.NewBiddersEndpoint(cfg.BidderInfos, defaultAliases)) r.GET("/info/bidders/:bidderName", infoEndpoints.NewBiddersDetailEndpoint(cfg.BidderInfos, defaultAliases)) r.GET("/bidders/params", NewJsonDirectoryServer(schemaDirectory, paramsValidator, defaultAliases)) - r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPermsBuilder, tcf2CfgBuilder, r.MetricsEngine, pbsAnalytics, accounts, activeBidders).Handle) + r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPermsBuilder, tcf2CfgBuilder, r.MetricsEngine, analyticsRunner, accounts, activeBidders).Handle) r.GET("/status", endpoints.NewStatusEndpoint(cfg.StatusResponse)) r.GET("/", serveIndex) r.Handler("GET", "/version", endpoints.NewVersionEndpoint(version.Ver, version.Rev)) @@ -267,7 +267,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R } // event endpoint - eventEndpoint := events.NewEventEndpoint(cfg, accounts, pbsAnalytics, r.MetricsEngine) + eventEndpoint := events.NewEventEndpoint(cfg, accounts, analyticsRunner, r.MetricsEngine) r.GET("/event", eventEndpoint) userSyncDeps := &pbs.UserSyncDeps{ @@ -277,7 +277,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R PriorityGroups: cfg.UserSync.PriorityGroups, } - r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg, syncersByBidder, gdprPermsBuilder, tcf2CfgBuilder, pbsAnalytics, accounts, r.MetricsEngine)) + r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg, syncersByBidder, gdprPermsBuilder, tcf2CfgBuilder, analyticsRunner, accounts, r.MetricsEngine)) r.GET("/getuids", endpoints.NewGetUIDsEndpoint(cfg.HostCookie)) r.POST("/optout", userSyncDeps.OptOut) r.GET("/optout", userSyncDeps.OptOut) From 324a9735e5e32210d2f1650321540e7544c4e777 Mon Sep 17 00:00:00 2001 From: bretg Date: Thu, 5 Oct 2023 02:01:14 -0400 Subject: [PATCH 035/138] update lunamedia contact info (#3137) --- static/bidder-info/lunamedia.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/bidder-info/lunamedia.yaml b/static/bidder-info/lunamedia.yaml index ef34143eb40..4064a566040 100644 --- a/static/bidder-info/lunamedia.yaml +++ b/static/bidder-info/lunamedia.yaml @@ -1,6 +1,6 @@ endpoint: "http://rtb.lunamedia.live/?pid={{.PublisherID}}" maintainer: - email: "josh@lunamedia.io" + email: "cs@lunamedia.io" capabilities: site: mediaTypes: From 8cdfe486d33a3cd33ad5ae544ebd706e64806b5b Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Thu, 5 Oct 2023 16:58:10 +0530 Subject: [PATCH 036/138] enable adapter alias feature (#3179) --- exchange/adapter_util.go | 4 ---- exchange/adapter_util_test.go | 7 ------- 2 files changed, 11 deletions(-) diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 2337f7f6bbb..1890635cb3d 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -33,10 +33,6 @@ func buildBidders(infos config.BidderInfos, builders map[openrtb_ext.BidderName] var errs []error for bidder, info := range infos { - if len(info.AliasOf) > 0 { - errs = append(errs, fmt.Errorf("This feature is currently under development")) - continue - } bidderName, bidderNameFound := openrtb_ext.NormalizeBidderName(bidder) if !bidderNameFound { errs = append(errs, fmt.Errorf("%v: unknown bidder", bidder)) diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index 611498fea0c..08d751cdadf 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -68,13 +68,6 @@ func TestBuildAdapters(t *testing.T) { errors.New("unknown: unknown bidder"), }, }, - { - description: "Alias feature disabled", - bidderInfos: map[string]config.BidderInfo{"appNexus": {AliasOf: "rubicon"}}, - expectedErrors: []error{ - errors.New("This feature is currently under development"), - }, - }, } cfg := &config.Configuration{} From 23bc394e8218bb3f6c860c37bd13c56ea50d3df1 Mon Sep 17 00:00:00 2001 From: fkoch-sc Date: Mon, 9 Oct 2023 08:48:29 +0200 Subject: [PATCH 037/138] New Adapter: smartx (#3109) Co-authored-by: Andreas Schubert Co-authored-by: schubert-sc <144821265+schubert-sc@users.noreply.github.com> --- adapters/smartx/params_test.go | 58 ++++++++ adapters/smartx/smartx.go | 90 +++++++++++++ adapters/smartx/smartx_test.go | 24 ++++ .../smartx/smartxtest/exemplary/01-video.json | 77 +++++++++++ .../smartxtest/exemplary/02-consent.json | 124 ++++++++++++++++++ .../smartxtest/exemplary/03-device.json | 121 +++++++++++++++++ .../02-internal-server-error.json | 45 +++++++ .../03-missing-bidder-in-response.json | 47 +++++++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_smartx.go | 10 ++ static/bidder-info/smartx.yaml | 12 ++ static/bidder-params/smartx.json | 37 ++++++ 13 files changed, 649 insertions(+) create mode 100644 adapters/smartx/params_test.go create mode 100644 adapters/smartx/smartx.go create mode 100644 adapters/smartx/smartx_test.go create mode 100644 adapters/smartx/smartxtest/exemplary/01-video.json create mode 100644 adapters/smartx/smartxtest/exemplary/02-consent.json create mode 100644 adapters/smartx/smartxtest/exemplary/03-device.json create mode 100644 adapters/smartx/smartxtest/supplemental/02-internal-server-error.json create mode 100644 adapters/smartx/smartxtest/supplemental/03-missing-bidder-in-response.json create mode 100644 openrtb_ext/imp_smartx.go create mode 100644 static/bidder-info/smartx.yaml create mode 100644 static/bidder-params/smartx.json diff --git a/adapters/smartx/params_test.go b/adapters/smartx/params_test.go new file mode 100644 index 00000000000..fd28f4ead9b --- /dev/null +++ b/adapters/smartx/params_test.go @@ -0,0 +1,58 @@ +package smartx + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{"tagId":"Nu68JuOWAvrbzoyrOR9a7A", "publisherId":"11986", "siteId":"22860"}`, + `{"tagId":"Nu68JuOWAvrbzoyrOR9a7A", "publisherId":"11986", "appId":"22860"}`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderSmartx, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected smartx params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `[]`, + `{}`, + `{"anyparam": "anyvalue"}`, + `{"tagId":"Nu68JuOWAvrbzoyrOR9a7A"}`, + `{"publisherId":"11986"}`, + `{"siteId":"22860"}`, + `{"appId":"22860"}`, + `{"tagId":"Nu68JuOWAvrbzoyrOR9a7A", "publisherId":"11986"}`, + `{"tagId":"Nu68JuOWAvrbzoyrOR9a7A", "siteId":"22860"}`, + `{"tagId":"Nu68JuOWAvrbzoyrOR9a7A", "appId":"22860"}`, + `{"publisherId":"11986", "appId":"22860"}`, + `{"publisherId":"11986", "appId":"22860"}`, +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderSmartHub, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/smartx/smartx.go b/adapters/smartx/smartx.go new file mode 100644 index 00000000000..6fbc94968ca --- /dev/null +++ b/adapters/smartx/smartx.go @@ -0,0 +1,90 @@ +package smartx + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpointURL string +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + return &adapter{ + endpointURL: config.Endpoint, + }, nil +} + +func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) { + openRTBRequestJSON, err := json.Marshal(openRTBRequest) + if err != nil { + errs = append(errs, fmt.Errorf("marshal bidRequest: %w", err)) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("x-openrtb-version", "2.5") + + if openRTBRequest.Device != nil { + if openRTBRequest.Device.UA != "" { + headers.Set("User-Agent", openRTBRequest.Device.UA) + } + + if openRTBRequest.Device.IP != "" { + headers.Set("Forwarded", "for="+openRTBRequest.Device.IP) + headers.Set("X-Forwarded-For", openRTBRequest.Device.IP) + } + } + + return append(requestsToBidder, &adapters.RequestData{ + Method: http.MethodPost, + Uri: a.endpointURL, + Body: openRTBRequestJSON, + Headers: headers, + }), nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + if len(response.SeatBid) == 0 { + return nil, []error{errors.New("no bidders found in JSON response")} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + if response.Cur != "" { + bidResponse.Currency = response.Cur + } + + var errs []error + + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: openrtb_ext.BidTypeVideo, + }) + } + } + + return bidResponse, errs +} diff --git a/adapters/smartx/smartx_test.go b/adapters/smartx/smartx_test.go new file mode 100644 index 00000000000..ba6f6ba7762 --- /dev/null +++ b/adapters/smartx/smartx_test.go @@ -0,0 +1,24 @@ +package smartx + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const testsDir = "smartxtest" +const testsBidderEndpoint = "https://bid.smartclip.net/bid/1005" + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder( + openrtb_ext.BidderRise, + config.Adapter{Endpoint: testsBidderEndpoint}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 115, DataCenter: "2"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, testsDir, bidder) +} diff --git a/adapters/smartx/smartxtest/exemplary/01-video.json b/adapters/smartx/smartxtest/exemplary/01-video.json new file mode 100644 index 00000000000..c4d3f9c5b31 --- /dev/null +++ b/adapters/smartx/smartxtest/exemplary/01-video.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest":{ + "id":"test-request-id-video", + "imp":[ + { + "id":"test-imp-id", + "video":{ + "mimes":[ + "video/mp4" + ] + } + } + ] + }, + "httpCalls":[ + { + "expectedRequest":{ + "uri":"https://bid.smartclip.net/bid/1005", + "body":{ + "id":"test-request-id-video", + "imp":[ + { + "id":"test-imp-id", + "video":{ + "mimes":[ + "video/mp4" + ] + } + } + ] + } + }, + "mockResponse":{ + "status":200, + "body":{ + "id":"test-request-id-video", + "seatbid":[ + { + "seat":"smartadserver", + "bid":[ + { + "id":"8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid":"test-imp-id-video", + "price":0.500000, + "adm":"some-test-ad", + "crid":"crid_10", + "h":576, + "w":1024, + "mtype":2 + } + ] + } + ], + "cur":"EUR" + } + } + } + ], + "expectedBidResponses":[ + { + "bids":[{ + "bid":{ + "id":"8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid":"test-imp-id-video", + "price":0.500000, + "adm":"some-test-ad", + "crid":"crid_10", + "h":576, + "w":1024, + "mtype":2 + }, + "currency":"EUR", + "type": "video" + }] + } + ] +} \ No newline at end of file diff --git a/adapters/smartx/smartxtest/exemplary/02-consent.json b/adapters/smartx/smartxtest/exemplary/02-consent.json new file mode 100644 index 00000000000..bc0e5604f4e --- /dev/null +++ b/adapters/smartx/smartxtest/exemplary/02-consent.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "prebid": { + "bidder": { + "smartx": { + "publisherId": "11986", + "tagId": "Nu68JuOWAvrbzoyrOR9a7A", + "siteId": "22860" + } + } + } + } + } + ], + "user": { + "ext": { + "consent": "COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.smartclip.net/bid/1005", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "prebid": { + "bidder": { + "smartx": { + "publisherId": "11986", + "tagId": "Nu68JuOWAvrbzoyrOR9a7A", + "siteId": "22860" + } + } + } + } + } + ], + "user": { + "ext": { + "consent": "COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "ext": { + "ix": {} + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smartx/smartxtest/exemplary/03-device.json b/adapters/smartx/smartxtest/exemplary/03-device.json new file mode 100644 index 00000000000..f45cc76c99a --- /dev/null +++ b/adapters/smartx/smartxtest/exemplary/03-device.json @@ -0,0 +1,121 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; SAMSUNG SM-G780G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/17.0 Chrome/96.0.4664.104 Mobile Safari/537.36", + "geo": { + "lat": 48.1663, + "lon": 11.5683, + "type": 2, + "country": "DEU", + "region": "BY", + "city": "Munich", + "zip": "81249", + "ipservice": 3 + }, + "dnt": 0, + "lmt": 0, + "ip": "0.0.0.0", + "devicetype": 4, + "make": "Samsung", + "model": "SM-G780G", + "os": "Android", + "language": "en" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.smartclip.net/bid/1005", + "body": { + "id": "test-request-id-video", + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; SAMSUNG SM-G780G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/17.0 Chrome/96.0.4664.104 Mobile Safari/537.36", + "geo": { + "lat": 48.1663, + "lon": 11.5683, + "type": 2, + "country": "DEU", + "region": "BY", + "city": "Munich", + "zip": "81249", + "ipservice": 3 + }, + "dnt": 0, + "lmt": 0, + "ip": "0.0.0.0", + "devicetype": 4, + "make": "Samsung", + "model": "SM-G780G", + "os": "Android", + "language": "en" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-video", + "seatbid": [ + { + "seat": "smartadserver", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-video", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 576, + "w": 1024, + "mtype": 2 + } + ] + } + ], + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-video", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 576, + "w": 1024, + "mtype": 2 + }, + "currency": "EUR", + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smartx/smartxtest/supplemental/02-internal-server-error.json b/adapters/smartx/smartxtest/supplemental/02-internal-server-error.json new file mode 100644 index 00000000000..d44ef7f77e1 --- /dev/null +++ b/adapters/smartx/smartxtest/supplemental/02-internal-server-error.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "test-internal-server-error-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.smartclip.net/bid/1005", + "body": { + "id": "test-internal-server-error-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smartx/smartxtest/supplemental/03-missing-bidder-in-response.json b/adapters/smartx/smartxtest/supplemental/03-missing-bidder-in-response.json new file mode 100644 index 00000000000..71875f12809 --- /dev/null +++ b/adapters/smartx/smartxtest/supplemental/03-missing-bidder-in-response.json @@ -0,0 +1,47 @@ +{ + "mockBidRequest": { + "id": "test-missing-bidder-in-response-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.smartclip.net/bid/1005", + "body": { + "id": "test-missing-bidder-in-response-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ] + } + }, + "mockResponse": { + "body": { + "seatbid": [] + }, + "status": 200 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "no bidders found in JSON response", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index c5b4b1134cc..95cb1626085 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -148,6 +148,7 @@ import ( "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smarthub" "github.com/prebid/prebid-server/adapters/smartrtb" + "github.com/prebid/prebid-server/adapters/smartx" "github.com/prebid/prebid-server/adapters/smartyads" "github.com/prebid/prebid-server/adapters/smilewanted" "github.com/prebid/prebid-server/adapters/sonobi" @@ -348,6 +349,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderSmartAdserver: smartadserver.Builder, openrtb_ext.BidderSmartHub: smarthub.Builder, openrtb_ext.BidderSmartRTB: smartrtb.Builder, + openrtb_ext.BidderSmartx: smartx.Builder, openrtb_ext.BidderSmartyAds: smartyads.Builder, openrtb_ext.BidderSmileWanted: smilewanted.Builder, openrtb_ext.BidderSonobi: sonobi.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 67f43ec62dd..475d56a7ba8 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -178,6 +178,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderSmartAdserver, BidderSmartHub, BidderSmartRTB, + BidderSmartx, BidderSmartyAds, BidderSmileWanted, BidderSonobi, @@ -466,6 +467,7 @@ const ( BidderSmartAdserver BidderName = "smartadserver" BidderSmartHub BidderName = "smarthub" BidderSmartRTB BidderName = "smartrtb" + BidderSmartx BidderName = "smartx" BidderSmartyAds BidderName = "smartyads" BidderSmileWanted BidderName = "smilewanted" BidderSonobi BidderName = "sonobi" diff --git a/openrtb_ext/imp_smartx.go b/openrtb_ext/imp_smartx.go new file mode 100644 index 00000000000..9a2975b01de --- /dev/null +++ b/openrtb_ext/imp_smartx.go @@ -0,0 +1,10 @@ +package openrtb_ext + +type ExtImpSmartclip struct { + TagID string `json:"tagId"` + PublisherID string `json:"publisherId"` + SiteID string `json:"siteId"` + AppID string `json:"appId"` + BundleID string `json:"bundleId"` + StoreURL string `json:"storeUrl"` +} diff --git a/static/bidder-info/smartx.yaml b/static/bidder-info/smartx.yaml new file mode 100644 index 00000000000..9a387ecfbd2 --- /dev/null +++ b/static/bidder-info/smartx.yaml @@ -0,0 +1,12 @@ +endpoint: "https://bid.smartclip.net/bid/1005" +maintainer: + email: "bidding@smartclip.tv" +gvlVendorID: 115 +modifyingVastXmlAllowed: false +capabilities: + site: + mediaTypes: + - video + app: + mediaTypes: + - video diff --git a/static/bidder-params/smartx.json b/static/bidder-params/smartx.json new file mode 100644 index 00000000000..3bd97456770 --- /dev/null +++ b/static/bidder-params/smartx.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "smartclip.tv Adapter Params", + "description": "A schema which validates params accepted by the smartclip.tv adapter", + "type": "object", + "properties": { + "tagId": { + "type": "string", + "description": "Ad tag ID" + }, + "publisherId": { + "type": "string", + "description": "Publisher ID" + }, + "siteId": { + "type": "string", + "description": "Site ID" + }, + "appId": { + "type": "string", + "description": "App ID" + }, + "bundleId": { + "type": "string", + "description": "Bundle ID" + }, + "storeUrl": { + "type": "string", + "description": "AppStore URL" + } + }, + "oneOf": [ + { "required": ["siteId"] }, + { "required": ["appId"] } + ], + "required": ["tagId", "publisherId"] +} \ No newline at end of file From 1944dbb8592872a712532abaf7a331b2a862b2ac Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 9 Oct 2023 15:46:46 -0400 Subject: [PATCH 038/138] Adapter Name Case Insensitive: EID Permissions (#3187) --- endpoints/openrtb2/amp_auction.go | 1 + endpoints/openrtb2/auction.go | 40 ++--- endpoints/openrtb2/auction_test.go | 65 ++++++-- ...idpermissions-insensitive-bidder-name.json | 109 ------------- endpoints/openrtb2/video_auction.go | 3 +- endpoints/openrtb2/video_auction_test.go | 4 + .../eidpermissions-allowed-alias.json | 12 +- ...dpermissions-allowed-case-insensitive.json | 144 ++++++++++++++++++ .../exchangetest/eidpermissions-allowed.json | 12 +- .../exchangetest/eidpermissions-denied.json | 6 +- exchange/utils.go | 3 +- exchange/utils_test.go | 8 + 12 files changed, 262 insertions(+), 145 deletions(-) delete mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/eidpermissions-insensitive-bidder-name.json create mode 100644 exchange/exchangetest/eidpermissions-allowed-case-insensitive.json diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 76762d8b9ea..2fde59c25e4 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -103,6 +103,7 @@ func NewAmpEndpoint( storedRespFetcher, hookExecutionPlanBuilder, tmaxAdjustments, + openrtb_ext.NormalizeBidderName, }).AmpAuction), nil } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 4fce4c15786..3a929c43abe 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -126,9 +126,12 @@ func NewEndpoint( ipValidator, storedRespFetcher, hookExecutionPlanBuilder, - tmaxAdjustments}).Auction), nil + tmaxAdjustments, + openrtb_ext.NormalizeBidderName}).Auction), nil } +type normalizeBidderName func(name string) (openrtb_ext.BidderName, bool) + type endpointDeps struct { uuidGenerator uuidutil.UUIDGenerator ex exchange.Exchange @@ -149,6 +152,7 @@ type endpointDeps struct { storedRespFetcher stored_requests.Fetcher hookExecutionPlanBuilder hooks.ExecutionPlanBuilder tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed + normalizeBidderName normalizeBidderName } func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { @@ -765,7 +769,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } } - var aliases map[string]string + var requestAliases map[string]string reqExt, err := req.GetRequestExt() if err != nil { return []error{fmt.Errorf("request.ext is invalid: %v", err)} @@ -777,17 +781,17 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } if reqPrebid != nil { - aliases = reqPrebid.Aliases + requestAliases = reqPrebid.Aliases - if err := deps.validateAliases(aliases); err != nil { + if err := deps.validateAliases(requestAliases); err != nil { return []error{err} } - if err := deps.validateAliasesGVLIDs(reqPrebid.AliasGVLIDs, aliases); err != nil { + if err := deps.validateAliasesGVLIDs(reqPrebid.AliasGVLIDs, requestAliases); err != nil { return []error{err} } - if err := deps.validateBidAdjustmentFactors(reqPrebid.BidAdjustmentFactors, aliases); err != nil { + if err := deps.validateBidAdjustmentFactors(reqPrebid.BidAdjustmentFactors, requestAliases); err != nil { return []error{err} } @@ -795,7 +799,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp return []error{err} } - if err := deps.validateEidPermissions(reqPrebid.Data, aliases); err != nil { + if err := deps.validateEidPermissions(reqPrebid.Data, requestAliases); err != nil { return []error{err} } @@ -844,7 +848,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } } - if errs := deps.validateUser(req, aliases, gpp); errs != nil { + if errs := deps.validateUser(req, requestAliases, gpp); errs != nil { if len(errs) > 0 { errL = append(errL, errs...) } @@ -871,7 +875,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp if errortypes.ContainsFatalError([]error{err}) { return errL } - } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); err != nil { + } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(requestAliases)); err != nil { if _, invalidConsent := err.(*errortypes.Warning); invalidConsent { errL = append(errL, &errortypes.Warning{ Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err), @@ -894,7 +898,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } impIDs[imp.ID] = i - errs := deps.validateImp(imp, aliases, i, hasStoredResponses, storedBidResp) + errs := deps.validateImp(imp, requestAliases, i, hasStoredResponses, storedBidResp) if len(errs) > 0 { errL = append(errL, errs...) } @@ -986,7 +990,7 @@ func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error { return err } -func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestPrebidData, aliases map[string]string) error { +func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestPrebidData, requestAliases map[string]string) error { if prebid == nil { return nil } @@ -1006,7 +1010,7 @@ func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestP return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] missing or empty required field: "bidders"`, i) } - if err := validateBidders(eid.Bidders, deps.bidderMap, aliases); err != nil { + if err := deps.validateBidders(eid.Bidders, deps.bidderMap, requestAliases); err != nil { return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] contains %v`, i, err) } } @@ -1014,20 +1018,16 @@ func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestP return nil } -func validateBidders(bidders []string, knownBidders map[string]openrtb_ext.BidderName, knownAliases map[string]string) error { +func (deps *endpointDeps) validateBidders(bidders []string, knownBidders map[string]openrtb_ext.BidderName, knownRequestAliases map[string]string) error { for _, bidder := range bidders { if bidder == "*" { if len(bidders) > 1 { return errors.New(`bidder wildcard "*" mixed with specific bidders`) } } else { - bidderName := bidder - normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidderName) - if ok { - bidderName = normalizedCoreBidder.String() - } - _, isCoreBidder := knownBidders[bidderName] - _, isAlias := knownAliases[bidder] + bidderNormalized, _ := deps.normalizeBidderName(bidder) + _, isCoreBidder := knownBidders[bidderNormalized.String()] + _, isAlias := knownRequestAliases[bidder] if !isCoreBidder && !isAlias { return fmt.Errorf(`unrecognized bidder "%v"`, bidder) } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 55bf68ff730..8204a80e3db 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1186,6 +1186,7 @@ func TestStoredRequests(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } testStoreVideoAttr := []bool{true, true, false, false, false} @@ -1635,6 +1636,7 @@ func TestValidateRequest(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } testCases := []struct { @@ -2414,6 +2416,7 @@ func TestSetIntegrationType(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } testCases := []struct { @@ -2480,6 +2483,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } req := &openrtb2.BidRequest{} @@ -2584,6 +2588,7 @@ func TestOversizedRequest(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -2623,6 +2628,7 @@ func TestRequestSizeEdgeCase(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -2962,6 +2968,7 @@ func TestValidateImpExt(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } for _, group := range testGroups { @@ -3017,6 +3024,7 @@ func TestCurrencyTrunc(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } ui := int64(1) @@ -3065,6 +3073,7 @@ func TestCCPAInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } ui := int64(1) @@ -3117,6 +3126,7 @@ func TestNoSaleInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } ui := int64(1) @@ -3172,6 +3182,7 @@ func TestValidateSourceTID(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } ui := int64(1) @@ -3217,6 +3228,7 @@ func TestSChainInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } ui := int64(1) @@ -3785,6 +3797,7 @@ func TestEidPermissionsInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } ui := int64(1) @@ -3853,6 +3866,13 @@ func TestValidateEidPermissions(t *testing.T) { }}}}, expectedError: nil, }, + { + description: "Valid - One - Case Insensitive", + request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ + {Source: "sourceA", Bidders: []string{"A"}}, + }}}}, + expectedError: nil, + }, { description: "Valid - Many", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ @@ -3901,9 +3921,16 @@ func TestValidateEidPermissions(t *testing.T) { }}}}, expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] contains unrecognized bidder "z"`), }, + { + description: "Valid - Alias Case Sensitive", + request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ + {Source: "sourceA", Bidders: []string{"B"}}, + }}}}, + expectedError: errors.New(`request.ext.prebid.data.eidpermissions[0] contains unrecognized bidder "B"`), + }, } - endpoint := &endpointDeps{bidderMap: knownBidders} + endpoint := &endpointDeps{bidderMap: knownBidders, normalizeBidderName: fakeNormalizeBidderName} for _, test := range testCases { result := endpoint.validateEidPermissions(test.request.Prebid.Data, knownAliases) assert.Equal(t, test.expectedError, result, test.description) @@ -3939,6 +3966,13 @@ func TestValidateBidders(t *testing.T) { knownAliases: map[string]string{"c": "c"}, expectedError: nil, }, + { + description: "Valid - One Core Bidder - Case Insensitive", + bidders: []string{"A"}, + knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, + knownAliases: map[string]string{"c": "c"}, + expectedError: nil, + }, { description: "Valid - Many Core Bidders", bidders: []string{"a", "b"}, @@ -3953,6 +3987,13 @@ func TestValidateBidders(t *testing.T) { knownAliases: map[string]string{"c": "c"}, expectedError: nil, }, + { + description: "Valid - One Alias Bidder - Case Sensitive", + bidders: []string{"C"}, + knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, + knownAliases: map[string]string{"c": "c"}, + expectedError: errors.New(`unrecognized bidder "C"`), + }, { description: "Valid - Many Alias Bidders", bidders: []string{"c", "d"}, @@ -3974,13 +4015,6 @@ func TestValidateBidders(t *testing.T) { knownAliases: map[string]string{"c": "c"}, expectedError: errors.New(`unrecognized bidder "z"`), }, - { - description: "Invalid - Unknown Bidder Case Sensitive", - bidders: []string{"A"}, - knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, - knownAliases: map[string]string{"c": "c"}, - expectedError: errors.New(`unrecognized bidder "A"`), - }, { description: "Invalid - Unknown Bidder With Known Bidders", bidders: []string{"a", "c", "z"}, @@ -4011,8 +4045,9 @@ func TestValidateBidders(t *testing.T) { }, } + endpoint := &endpointDeps{normalizeBidderName: fakeNormalizeBidderName} for _, test := range testCases { - result := validateBidders(test.bidders, test.knownBidders, test.knownAliases) + result := endpoint.validateBidders(test.bidders, test.knownBidders, test.knownAliases) assert.Equal(t, test.expectedError, result, test.description) } } @@ -4102,6 +4137,7 @@ func TestAuctionWarnings(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { @@ -4148,6 +4184,7 @@ func TestParseRequestParseImpInfoError(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) @@ -4228,6 +4265,7 @@ func TestParseGzipedRequest(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) @@ -4828,6 +4866,7 @@ func TestParseRequestMergeBidderParams(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) @@ -4931,6 +4970,7 @@ func TestParseRequestStoredResponses(t *testing.T) { &mockStoredResponseFetcher{mockStoredResponses}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) @@ -5029,6 +5069,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) { &mockStoredResponseFetcher{mockStoredBidResponses}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) @@ -5065,6 +5106,7 @@ func TestValidateStoredResp(t *testing.T) { &mockStoredResponseFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } testCases := []struct { @@ -5878,6 +5920,7 @@ func TestParseRequestMultiBid(t *testing.T) { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) @@ -6043,3 +6086,7 @@ func TestValidateAliases(t *testing.T) { }) } } + +func fakeNormalizeBidderName(name string) (openrtb_ext.BidderName, bool) { + return openrtb_ext.BidderName(strings.ToLower(name)), true +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/eidpermissions-insensitive-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/eidpermissions-insensitive-bidder-name.json deleted file mode 100644 index b06b2593e26..00000000000 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/eidpermissions-insensitive-bidder-name.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "description": "This demonstrates ext.prebid.data.eidpermissions.bidders with case insensitive bidder names", - "config": { - "mockBidders": [ - { - "bidderName": "appnexus", - "currency": "USD", - "price": 1.00 - }, - { - "bidderName": "rubicon", - "currency": "USD", - "price": 1.00 - } - ] - }, - "mockBidRequest": { - "id": "some-request-id", - "site": { - "page": "prebid.org" - }, - "user": { - "ext": { - "consent": "gdpr-consent-string" - } - }, - "regs": { - "ext": { - "gdpr": 1, - "us_privacy": "1NYN" - } - }, - "imp": [ - { - "id": "some-impression-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "APPNEXUS": { - "placementId": 12883451 - } - } - } - ], - "tmax": 500, - "ext": { - "prebid": { - "data": { - "eidpermissions": [ - { - "source": "source1", - "bidders": [ - "APPNEXUS" - ] - } - ] - }, - "cache": { - "bids": {} - }, - "channel": { - "name": "video", - "version": "1.0" - }, - "targeting": { - "includewinners": false, - "pricegranularity": { - "precision": 2, - "ranges": [ - { - "max": 20, - "increment": 0.10 - } - ] - } - } - } - } - }, - "expectedBidResponse": { - "id": "some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 1 - } - ], - "seat": "APPNEXUS" - } - ], - "bidid": "test bid id", - "cur": "USD", - "nbr": 0 - }, - "expectedReturnCode": 200 -} \ No newline at end of file diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index fc6d2a91a62..8faf15543d3 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -92,7 +92,8 @@ func NewVideoEndpoint( ipValidator, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, - tmaxAdjustments}).VideoAuctionEndpoint), nil + tmaxAdjustments, + openrtb_ext.NormalizeBidderName}).VideoAuctionEndpoint), nil } /* diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 181a68929ed..19af821f205 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1227,6 +1227,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *m empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } return deps, metrics, mockModule } @@ -1274,6 +1275,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } } @@ -1298,6 +1300,7 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } return deps @@ -1324,6 +1327,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, nil, + openrtb_ext.NormalizeBidderName, } return edep diff --git a/exchange/exchangetest/eidpermissions-allowed-alias.json b/exchange/exchangetest/eidpermissions-allowed-alias.json index f10bdcf86c0..de3c0e1dca6 100644 --- a/exchange/exchangetest/eidpermissions-allowed-alias.json +++ b/exchange/exchangetest/eidpermissions-allowed-alias.json @@ -10,7 +10,11 @@ "eids": [ { "source": "source1", - "id": "anyId" + "uids": [ + { + "id": "id1" + } + ] } ] } @@ -66,7 +70,11 @@ "eids": [ { "source": "source1", - "id": "anyId" + "uids": [ + { + "id": "id1" + } + ] } ] } diff --git a/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json b/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json new file mode 100644 index 00000000000..86797501495 --- /dev/null +++ b/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json @@ -0,0 +1,144 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "ext": { + "eids": [ + { + "source": "source1", + "uids": [ + { + "id": "id1" + } + ] + } + ] + } + }, + "ext": { + "prebid": { + "data": { + "eidpermissions": [ + { + "source": "source1", + "bidders": [ + "APPNEXUS" + ] + } + ] + } + } + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "ext": { + "eids": [ + { + "source": "source1", + "uids": [ + { + "id": "id1" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ] + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "type": "video" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/eidpermissions-allowed.json b/exchange/exchangetest/eidpermissions-allowed.json index 6b27212cbcf..9daee27a9b6 100644 --- a/exchange/exchangetest/eidpermissions-allowed.json +++ b/exchange/exchangetest/eidpermissions-allowed.json @@ -10,7 +10,11 @@ "eids": [ { "source": "source1", - "id": "anyId" + "uids": [ + { + "id": "id1" + } + ] } ] } @@ -63,7 +67,11 @@ "eids": [ { "source": "source1", - "id": "anyId" + "uids": [ + { + "id": "id1" + } + ] } ] } diff --git a/exchange/exchangetest/eidpermissions-denied.json b/exchange/exchangetest/eidpermissions-denied.json index df112905235..04eb51bb600 100644 --- a/exchange/exchangetest/eidpermissions-denied.json +++ b/exchange/exchangetest/eidpermissions-denied.json @@ -10,7 +10,11 @@ "eids": [ { "source": "source1", - "id": "anyId" + "uids": [ + { + "id": "id1" + } + ] } ] } diff --git a/exchange/utils.go b/exchange/utils.go index 0a38e487632..0d9c90cda99 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "math/rand" + "strings" "github.com/buger/jsonparser" "github.com/prebid/go-gdpr/vendorconsent" @@ -710,7 +711,7 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque allowed := false if rule, hasRule := eidRules[eid.Source]; hasRule { for _, ruleBidder := range rule { - if ruleBidder == "*" || ruleBidder == bidder { + if ruleBidder == "*" || strings.EqualFold(ruleBidder, bidder) { allowed = true break } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index ee6dad1ad96..0dbf5b38e5c 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -2768,6 +2768,14 @@ func TestRemoveUnpermissionedEids(t *testing.T) { }, expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), }, + { + description: "Allowed By Specific Bidder - Case Insensitive", + userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ + {Source: "source1", Bidders: []string{"BIDDERA"}}, + }, + expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + }, { description: "Allowed By All Bidders", userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), From e76b4b392ba8cd52893dac12d30913f6a71174db Mon Sep 17 00:00:00 2001 From: Emanuele Simonelli <136568337+EmanueleSimonelli@users.noreply.github.com> Date: Tue, 10 Oct 2023 09:15:31 +0200 Subject: [PATCH 039/138] OneTag: ORTB 2.6 initial support (#3128) Co-authored by @EmanueleSimonelli --- static/bidder-info/onetag.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/bidder-info/onetag.yaml b/static/bidder-info/onetag.yaml index 3677921ee5c..bc52faffe1b 100644 --- a/static/bidder-info/onetag.yaml +++ b/static/bidder-info/onetag.yaml @@ -1,4 +1,6 @@ endpoint: "https://prebid-server.onetag-sys.com/prebid-server/{{.PublisherID}}" +openrtb: + version: 2.6 maintainer: email: devops@onetag.com gvlVendorID: 241 From c8f3b2df7a1ef96cee637d04009fad90cff9b36c Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 10 Oct 2023 13:24:43 -0400 Subject: [PATCH 040/138] Adapter Name Case Insensitive: SetUID Endpoint (#3186) --- endpoints/setuid.go | 11 +++++-- endpoints/setuid_test.go | 63 ++++++++++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/endpoints/setuid.go b/endpoints/setuid.go index 1bf3b575273..7af70d002b5 100644 --- a/endpoints/setuid.go +++ b/endpoints/setuid.go @@ -18,6 +18,7 @@ import ( "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/privacy" gppPrivacy "github.com/prebid/prebid-server/privacy/gpp" "github.com/prebid/prebid-server/stored_requests" @@ -329,7 +330,13 @@ func getSyncer(query url.Values, syncersByBidder map[string]usersync.Syncer) (us return nil, "", errors.New(`"bidder" query param is required`) } - syncer, syncerExists := syncersByBidder[bidder] + // case insensitive comparison + bidderNormalized, bidderFound := openrtb_ext.NormalizeBidderName(bidder) + if !bidderFound { + return nil, "", errors.New("The bidder name provided is not supported by Prebid Server") + } + + syncer, syncerExists := syncersByBidder[bidderNormalized.String()] if !syncerExists { return nil, "", errors.New("The bidder name provided is not supported by Prebid Server") } @@ -340,7 +347,7 @@ func getSyncer(query url.Values, syncersByBidder map[string]usersync.Syncer) (us func isSyncerPriority(bidderNameFromSyncerQuery string, priorityGroups [][]string) bool { for _, group := range priorityGroups { for _, bidder := range group { - if bidderNameFromSyncerQuery == bidder { + if strings.EqualFold(bidderNameFromSyncerQuery, bidder) { return true } } diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 3636f7fbbdc..387de6bd05f 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -50,6 +50,16 @@ func TestSetUIDEndpoint(t *testing.T) { expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, description: "Set uid for valid bidder", }, + { + uri: "/setuid?bidder=PUBMATIC&uid=123", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "Set uid for valid bidder case insensitive", + }, { uri: "/setuid?bidder=appnexus&uid=123", syncersBidderNameToKey: map[string]string{"appnexus": "adnxs"}, @@ -1458,35 +1468,44 @@ func TestIsSyncerPriority(t *testing.T) { expected bool }{ { - name: "bidder-name-is-priority", - givenBidderNameFromSyncerQuery: "priorityBidder", - givenPriorityGroups: [][]string{ - {"priorityBidder"}, - {"2", "3"}, - }, - expected: true, + name: "priority-tier-1", + givenBidderNameFromSyncerQuery: "a", + givenPriorityGroups: [][]string{{"a"}}, + expected: true, }, { - name: "bidder-name-is-not-priority", - givenBidderNameFromSyncerQuery: "notPriorityBidderName", - givenPriorityGroups: [][]string{ - {"1"}, - {"2", "3"}, - }, - expected: false, + name: "priority-tier-other", + givenBidderNameFromSyncerQuery: "c", + givenPriorityGroups: [][]string{{"a"}, {"b", "c"}}, + expected: true, }, { - name: "no-bidder-name-given", + name: "priority-case-insensitive", + givenBidderNameFromSyncerQuery: "A", + givenPriorityGroups: [][]string{{"a"}}, + expected: true, + }, + { + name: "not-priority-empty", + givenBidderNameFromSyncerQuery: "a", + givenPriorityGroups: [][]string{}, + expected: false, + }, + { + name: "not-priority-not-defined", + givenBidderNameFromSyncerQuery: "a", + givenPriorityGroups: [][]string{{"b"}}, + expected: false, + }, + { + name: "no-bidder", givenBidderNameFromSyncerQuery: "", - givenPriorityGroups: [][]string{ - {"1"}, - {"2", "3"}, - }, - expected: false, + givenPriorityGroups: [][]string{{"b"}}, + expected: false, }, { - name: "no-priority-groups-given", - givenBidderNameFromSyncerQuery: "bidderName", + name: "no-priority-groups", + givenBidderNameFromSyncerQuery: "a", givenPriorityGroups: [][]string{}, expected: false, }, From 81c78ff026e3e0024f0affaaa5adaf4dbe8dec8b Mon Sep 17 00:00:00 2001 From: Nilesh Chate <97721111+pm-nilesh-chate@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:11:45 +0530 Subject: [PATCH 041/138] pubmatic: add fledge support (#3174) --- adapters/pubmatic/pubmatic.go | 25 +++ .../pubmatictest/exemplary/fledge.json | 166 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 adapters/pubmatic/pubmatictest/exemplary/fledge.json diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index 04bd00cb179..bd406d68631 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -19,6 +19,8 @@ import ( const MAX_IMPRESSIONS_PUBMATIC = 30 +const ae = "ae" + type PubmaticAdapter struct { URI string } @@ -42,6 +44,7 @@ type pubmaticBidExtVideo struct { type ExtImpBidderPubmatic struct { adapters.ExtImpBidder Data json.RawMessage `json:"data,omitempty"` + AE int `json:"ae,omitempty"` } type ExtAdServer struct { @@ -60,6 +63,10 @@ type extRequestAdServer struct { openrtb_ext.ExtRequest } +type respExt struct { + FledgeAuctionConfigs map[string]json.RawMessage `json:"fledge_auction_configs,omitempty"` +} + const ( dctrKeyName = "key_val" pmZoneIDKeyName = "pmZoneId" @@ -304,6 +311,10 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP populateFirstPartyDataImpAttributes(bidderExt.Data, extMap) } + if bidderExt.AE != 0 { + extMap[ae] = bidderExt.AE + } + imp.Ext = nil if len(extMap) > 0 { ext, err := json.Marshal(extMap) @@ -465,6 +476,20 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa if bidResp.Cur != "" { bidResponse.Currency = bidResp.Cur } + + if bidResp.Ext != nil { + var bidRespExt respExt + if err := json.Unmarshal(bidResp.Ext, &bidRespExt); err == nil && bidRespExt.FledgeAuctionConfigs != nil { + bidResponse.FledgeAuctionConfigs = make([]*openrtb_ext.FledgeAuctionConfig, 0, len(bidRespExt.FledgeAuctionConfigs)) + for impId, config := range bidRespExt.FledgeAuctionConfigs { + fledgeAuctionConfig := &openrtb_ext.FledgeAuctionConfig{ + ImpId: impId, + Config: config, + } + bidResponse.FledgeAuctionConfigs = append(bidResponse.FledgeAuctionConfigs, fledgeAuctionConfig) + } + } + } return bidResponse, errs } diff --git a/adapters/pubmatic/pubmatictest/exemplary/fledge.json b/adapters/pubmatic/pubmatictest/exemplary/fledge.json new file mode 100644 index 00000000000..793f0984624 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/exemplary/fledge.json @@ -0,0 +1,166 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "ae": 1, + "bidder": { + "publisherId": "999", + "adSlot": "AdTag_Div1@300x250" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ], + "h": 250, + "w": 300 + }, + "tagid": "AdTag_Div1", + "ext": { + "ae": 1 + } + } + ], + "ext": {"prebid":{}} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "pubmatic", + "bid": [ + { + "id": "9d8e77c2-ee0f-4781-af59-5359493b11af", + "impid": "test-imp-id", + "price": 1.1, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "ext": {} + } + ] + } + ], + "cur": "USD", + "ext": { + "fledge_auction_configs": { + "test-imp-id": { + "seller": "PUBMATIC_SELLER_CONSTANT_STRING", + "sellerTimeout": 123, + "decisionLogicUrl": "PUBMATIC_URL_CONSTANT_STRING", + "interestGroupBuyers": [ + "somedomain1.com", + "somedomain2.com", + "somedomain3.com", + "somedomain4.com" + ], + "perBuyerSignals": { + "somedomain1.com": { + "multiplier": 1, + "win_reporting_id": "1234" + }, + "somedomain2.com": { + "multiplier": 2, + "win_reporting_id": "2345" + }, + "somedomain3.com": { + "multiplier": 3, + "win_reporting_id": "3456" + }, + "somedomain4.com": { + "multiplier": 4, + "win_reporting_id": "4567" + } + } + } + } + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "9d8e77c2-ee0f-4781-af59-5359493b11af", + "impid": "test-imp-id", + "price": 1.1, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90, + "ext": {} + }, + "type": "banner" + } + ], + "fledgeauctionconfigs": [ + { + "impid": "test-imp-id", + "config": { + "seller": "PUBMATIC_SELLER_CONSTANT_STRING", + "sellerTimeout": 123, + "decisionLogicUrl": "PUBMATIC_URL_CONSTANT_STRING", + "interestGroupBuyers": [ + "somedomain1.com", + "somedomain2.com", + "somedomain3.com", + "somedomain4.com" + ], + "perBuyerSignals": { + "somedomain1.com": { + "multiplier": 1, + "win_reporting_id": "1234" + }, + "somedomain2.com": { + "multiplier": 2, + "win_reporting_id": "2345" + }, + "somedomain3.com": { + "multiplier": 3, + "win_reporting_id": "3456" + }, + "somedomain4.com": { + "multiplier": 4, + "win_reporting_id": "4567" + } + } + } + } + ] + } + ] +} \ No newline at end of file From 3c7d8528cad8cd4d1f598b50197e345ddb0bec24 Mon Sep 17 00:00:00 2001 From: Edge226Ads <144908224+Edge226Ads@users.noreply.github.com> Date: Mon, 16 Oct 2023 11:44:17 +0300 Subject: [PATCH 042/138] New Adapter: Edge226 (#3105) --- adapters/edge226/edge226.go | 145 ++++++++++++++++++ adapters/edge226/edge226_test.go | 20 +++ .../edge226test/exemplary/endpointId.json | 125 +++++++++++++++ .../edge226test/exemplary/simple-banner.json | 125 +++++++++++++++ .../edge226test/exemplary/simple-native.json | 109 +++++++++++++ .../edge226test/exemplary/simple-video.json | 120 +++++++++++++++ .../exemplary/simple-web-banner.json | 125 +++++++++++++++ .../supplemental/bad_media_type.json | 82 ++++++++++ .../supplemental/bad_response.json | 84 ++++++++++ .../edge226test/supplemental/status-204.json | 79 ++++++++++ .../supplemental/status-not-200.json | 84 ++++++++++ adapters/edge226/params_test.go | 47 ++++++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_edge226.go | 6 + static/bidder-info/edge226.yaml | 16 ++ static/bidder-params/edge226.json | 23 +++ 17 files changed, 1194 insertions(+) create mode 100644 adapters/edge226/edge226.go create mode 100644 adapters/edge226/edge226_test.go create mode 100644 adapters/edge226/edge226test/exemplary/endpointId.json create mode 100644 adapters/edge226/edge226test/exemplary/simple-banner.json create mode 100644 adapters/edge226/edge226test/exemplary/simple-native.json create mode 100644 adapters/edge226/edge226test/exemplary/simple-video.json create mode 100644 adapters/edge226/edge226test/exemplary/simple-web-banner.json create mode 100644 adapters/edge226/edge226test/supplemental/bad_media_type.json create mode 100644 adapters/edge226/edge226test/supplemental/bad_response.json create mode 100644 adapters/edge226/edge226test/supplemental/status-204.json create mode 100644 adapters/edge226/edge226test/supplemental/status-not-200.json create mode 100644 adapters/edge226/params_test.go create mode 100644 openrtb_ext/imp_edge226.go create mode 100644 static/bidder-info/edge226.yaml create mode 100644 static/bidder-params/edge226.json diff --git a/adapters/edge226/edge226.go b/adapters/edge226/edge226.go new file mode 100644 index 00000000000..a124cb47eca --- /dev/null +++ b/adapters/edge226/edge226.go @@ -0,0 +1,145 @@ +package edge226 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +type reqBodyExt struct { + Edge226BidderExt reqBodyExtBidder `json:"bidder"` +} + +type reqBodyExtBidder struct { + Type string `json:"type"` + PlacementID string `json:"placementId,omitempty"` + EndpointID string `json:"endpointId,omitempty"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var err error + var adapterRequests []*adapters.RequestData + + reqCopy := *request + for _, imp := range request.Imp { + reqCopy.Imp = []openrtb2.Imp{imp} + + var bidderExt adapters.ExtImpBidder + var edge226Ext openrtb_ext.ImpExtEdge226 + + if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { + return nil, []error{err} + } + if err = json.Unmarshal(bidderExt.Bidder, &edge226Ext); err != nil { + return nil, []error{err} + } + + temp := reqBodyExt{Edge226BidderExt: reqBodyExtBidder{}} + + if edge226Ext.PlacementID != "" { + temp.Edge226BidderExt.PlacementID = edge226Ext.PlacementID + temp.Edge226BidderExt.Type = "publisher" + } else if edge226Ext.EndpointID != "" { + temp.Edge226BidderExt.EndpointID = edge226Ext.EndpointID + temp.Edge226BidderExt.Type = "network" + } + + finalyImpExt, err := json.Marshal(temp) + if err != nil { + return nil, []error{err} + } + + reqCopy.Imp[0].Ext = finalyImpExt + + adapterReq, err := a.makeRequest(&reqCopy) + if err != nil { + return nil, []error{err} + } + + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + } + return adapterRequests, nil +} + +func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) { + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, err + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: reqJSON, + Headers: headers, + }, err +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidType, err := getBidMediaType(&seatBid.Bid[i]) + if err != nil { + return nil, []error{err} + } + + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +func getBidMediaType(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("Unable to fetch mediaType in multi-format: %s", bid.ImpID) + } +} diff --git a/adapters/edge226/edge226_test.go b/adapters/edge226/edge226_test.go new file mode 100644 index 00000000000..c5e40ce726d --- /dev/null +++ b/adapters/edge226/edge226_test.go @@ -0,0 +1,20 @@ +package edge226 + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderEdge226, config.Adapter{ + Endpoint: "http://test.com/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "edge226test", bidder) +} diff --git a/adapters/edge226/edge226test/exemplary/endpointId.json b/adapters/edge226/edge226test/exemplary/endpointId.json new file mode 100644 index 00000000000..741c1f8cb9b --- /dev/null +++ b/adapters/edge226/edge226test/exemplary/endpointId.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "endpointId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "endpointId": "test", + "type": "network" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1 + } + ], + "seat": "edge226" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/edge226/edge226test/exemplary/simple-banner.json b/adapters/edge226/edge226test/exemplary/simple-banner.json new file mode 100644 index 00000000000..741c1f8cb9b --- /dev/null +++ b/adapters/edge226/edge226test/exemplary/simple-banner.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "endpointId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "endpointId": "test", + "type": "network" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1 + } + ], + "seat": "edge226" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/edge226/edge226test/exemplary/simple-native.json b/adapters/edge226/edge226test/exemplary/simple-native.json new file mode 100644 index 00000000000..581864cdb41 --- /dev/null +++ b/adapters/edge226/edge226test/exemplary/simple-native.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 4 + } + ], + "seat": "edge226" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/edge226/edge226test/exemplary/simple-video.json b/adapters/edge226/edge226test/exemplary/simple-video.json new file mode 100644 index 00000000000..c14a1bd988e --- /dev/null +++ b/adapters/edge226/edge226test/exemplary/simple-video.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "mtype": 2 + } + ], + "seat": "edge226" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/edge226/edge226test/exemplary/simple-web-banner.json b/adapters/edge226/edge226test/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..6cc7295003c --- /dev/null +++ b/adapters/edge226/edge226test/exemplary/simple-web-banner.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123", + "ua": "Ubuntu" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123", + "ua": "Ubuntu" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "mtype": 1 + } + ], + "seat": "edge226" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/edge226/edge226test/supplemental/bad_media_type.json b/adapters/edge226/edge226test/supplemental/bad_media_type.json new file mode 100644 index 00000000000..75b13412ad6 --- /dev/null +++ b/adapters/edge226/edge226test/supplemental/bad_media_type.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": {} + } + ], + "seat": "edge226" + } + ], + "cur": "USD" + } + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unable to fetch mediaType in multi-format: test-imp-id", + "comparison": "literal" + } + ] +} diff --git a/adapters/edge226/edge226test/supplemental/bad_response.json b/adapters/edge226/edge226test/supplemental/bad_response.json new file mode 100644 index 00000000000..1e5664bafec --- /dev/null +++ b/adapters/edge226/edge226test/supplemental/bad_response.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/edge226/edge226test/supplemental/status-204.json b/adapters/edge226/edge226test/supplemental/status-204.json new file mode 100644 index 00000000000..da929ce61cb --- /dev/null +++ b/adapters/edge226/edge226test/supplemental/status-204.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }], + "expectedBidResponses": [] +} diff --git a/adapters/edge226/edge226test/supplemental/status-not-200.json b/adapters/edge226/edge226test/supplemental/status-not-200.json new file mode 100644 index 00000000000..cf1847c2cac --- /dev/null +++ b/adapters/edge226/edge226test/supplemental/status-not-200.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 404, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 404. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/edge226/params_test.go b/adapters/edge226/params_test.go new file mode 100644 index 00000000000..3191bc62136 --- /dev/null +++ b/adapters/edge226/params_test.go @@ -0,0 +1,47 @@ +package edge226 + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderEdge226, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderEdge226, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"placementId": "test"}`, + `{"placementId": "1"}`, + `{"endpointId": "test"}`, + `{"endpointId": "1"}`, +} + +var invalidParams = []string{ + `{"placementId": 42}`, + `{"endpointId": 42}`, + `{"placementId": "1", "endpointId": "1"}`, +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 95cb1626085..5d06ba9ba43 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -74,6 +74,7 @@ import ( "github.com/prebid/prebid-server/adapters/dianomi" "github.com/prebid/prebid-server/adapters/dmx" evolution "github.com/prebid/prebid-server/adapters/e_volution" + "github.com/prebid/prebid-server/adapters/edge226" "github.com/prebid/prebid-server/adapters/emtv" "github.com/prebid/prebid-server/adapters/eplanning" "github.com/prebid/prebid-server/adapters/epom" @@ -263,6 +264,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderDecenterAds: decenterads.Builder, openrtb_ext.BidderDeepintent: deepintent.Builder, openrtb_ext.BidderDianomi: dianomi.Builder, + openrtb_ext.BidderEdge226: edge226.Builder, openrtb_ext.BidderDmx: dmx.Builder, openrtb_ext.BidderEmtv: emtv.Builder, openrtb_ext.BidderEmxDigital: cadentaperturemx.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 475d56a7ba8..fd5ae176ab1 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -92,6 +92,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderDecenterAds, BidderDeepintent, BidderDianomi, + BidderEdge226, BidderDmx, BidderEmtv, BidderEmxDigital, @@ -381,6 +382,7 @@ const ( BidderDecenterAds BidderName = "decenterads" BidderDeepintent BidderName = "deepintent" BidderDianomi BidderName = "dianomi" + BidderEdge226 BidderName = "edge226" BidderDmx BidderName = "dmx" BidderEmtv BidderName = "emtv" BidderEmxDigital BidderName = "emx_digital" diff --git a/openrtb_ext/imp_edge226.go b/openrtb_ext/imp_edge226.go new file mode 100644 index 00000000000..44f3490ee42 --- /dev/null +++ b/openrtb_ext/imp_edge226.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtEdge226 struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` +} diff --git a/static/bidder-info/edge226.yaml b/static/bidder-info/edge226.yaml new file mode 100644 index 00000000000..c4fbdefc29b --- /dev/null +++ b/static/bidder-info/edge226.yaml @@ -0,0 +1,16 @@ +endpoint: "http://ssp.dauup.com/pserver" +maintainer: + email: "audit@edge226.com" +gvlVendorID: 1202 +capabilities: + site: + mediaTypes: + - banner + - video + - native + + app: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-params/edge226.json b/static/bidder-params/edge226.json new file mode 100644 index 00000000000..95b0fbdabb8 --- /dev/null +++ b/static/bidder-params/edge226.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Edge226 Adapter Params", + "description": "A schema which validates params accepted by the Edge226 adapter", + "type": "object", + + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + }, + "endpointId": { + "type": "string", + "minLength": 1, + "description": "Endpoint ID" + } + }, + "oneOf": [ + { "required": ["placementId"] }, + { "required": ["endpointId"] } + ] + } \ No newline at end of file From 6f4bb597a93a34d2cd08ef99c811fb316c3b511b Mon Sep 17 00:00:00 2001 From: Maxime Liege <56251840+github-maxime-liege@users.noreply.github.com> Date: Mon, 16 Oct 2023 10:46:35 +0200 Subject: [PATCH 043/138] New Adapter: Teads (#3112) --- adapters/teads/models.go | 40 ++++ adapters/teads/teads.go | 202 ++++++++++++++++++ adapters/teads/teads_test.go | 27 +++ .../exemplary/simple-banner-with-format.json | 168 +++++++++++++++ .../teadstest/exemplary/simple-banner.json | 158 ++++++++++++++ .../teadstest/exemplary/simple-video.json | 184 ++++++++++++++++ .../supplemental/bid-id-does-not-match.json | 152 +++++++++++++ .../supplemental/currency-empty-string.json | 185 ++++++++++++++++ .../supplemental/no-impression-response.json | 184 ++++++++++++++++ .../teadstest/supplemental/no-impression.json | 12 ++ .../supplemental/no-placementId.json | 28 +++ .../supplemental/renderer-name-empty.json | 143 +++++++++++++ .../supplemental/renderer-version-empty.json | 143 +++++++++++++ .../teadstest/supplemental/status-400.json | 65 ++++++ .../teadstest/supplemental/status-500.json | 65 ++++++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_teads.go | 5 + static/bidder-info/teads.yaml | 9 + static/bidder-params/teads.json | 15 ++ 20 files changed, 1789 insertions(+) create mode 100644 adapters/teads/models.go create mode 100644 adapters/teads/teads.go create mode 100644 adapters/teads/teads_test.go create mode 100644 adapters/teads/teadstest/exemplary/simple-banner-with-format.json create mode 100644 adapters/teads/teadstest/exemplary/simple-banner.json create mode 100644 adapters/teads/teadstest/exemplary/simple-video.json create mode 100644 adapters/teads/teadstest/supplemental/bid-id-does-not-match.json create mode 100644 adapters/teads/teadstest/supplemental/currency-empty-string.json create mode 100644 adapters/teads/teadstest/supplemental/no-impression-response.json create mode 100644 adapters/teads/teadstest/supplemental/no-impression.json create mode 100644 adapters/teads/teadstest/supplemental/no-placementId.json create mode 100644 adapters/teads/teadstest/supplemental/renderer-name-empty.json create mode 100644 adapters/teads/teadstest/supplemental/renderer-version-empty.json create mode 100644 adapters/teads/teadstest/supplemental/status-400.json create mode 100644 adapters/teads/teadstest/supplemental/status-500.json create mode 100644 openrtb_ext/imp_teads.go create mode 100644 static/bidder-info/teads.yaml create mode 100644 static/bidder-params/teads.json diff --git a/adapters/teads/models.go b/adapters/teads/models.go new file mode 100644 index 00000000000..5b63c163197 --- /dev/null +++ b/adapters/teads/models.go @@ -0,0 +1,40 @@ +package teads + +import ( + "encoding/json" + "text/template" +) + +type adapter struct { + endpointTemplate *template.Template +} + +type defaultBidderImpExtension struct { + Bidder bidder `json:"bidder"` +} + +type bidder struct { + PlacementId int `json:"placementId"` +} + +type teadsImpExtension struct { + KV teadsKV `json:"kv"` +} + +type teadsKV struct { + PlacementId int `json:"placementId"` +} + +type teadsBidExt struct { + Prebid teadsPrebidExt `json:"prebid"` +} + +type teadsPrebidExt struct { + Meta teadsPrebidMeta `json:"meta"` +} + +type teadsPrebidMeta struct { + RendererName string `json:"rendererName"` + RendererVersion string `json:"rendererVersion"` + RendererData json.RawMessage `json:"rendererData"` +} diff --git a/adapters/teads/teads.go b/adapters/teads/teads.go new file mode 100644 index 00000000000..9c2bb57fc57 --- /dev/null +++ b/adapters/teads/teads.go @@ -0,0 +1,202 @@ +package teads + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "text/template" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// Builder builds a new instance of the Teads adapter for the given bidder with the given config. +func Builder(_ openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpointTemplate: template, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "No impression in the bid request", + }} + } + + endpointURL, err := a.buildEndpointURL() + if endpointURL == "" { + return nil, []error{err} + } + + if err := updateImpObject(request.Imp); err != nil { + return nil, []error{err} + } + + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: "Error parsing BidRequest object", + }} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + return []*adapters.RequestData{{ + Method: "POST", + Uri: endpointURL, + Body: reqJSON, + Headers: headers, + }}, []error{} +} + +func updateImpObject(imps []openrtb2.Imp) error { + for i := range imps { + imp := &imps[i] + + if imp.Banner != nil { + if len(imp.Banner.Format) != 0 { + bannerCopy := *imp.Banner + bannerCopy.H = &imp.Banner.Format[0].H + bannerCopy.W = &imp.Banner.Format[0].W + imp.Banner = &bannerCopy + } + } + + var defaultImpExt defaultBidderImpExtension + if err := json.Unmarshal(imp.Ext, &defaultImpExt); err != nil { + return &errortypes.BadInput{ + Message: "Error parsing Imp.Ext object", + } + } + if defaultImpExt.Bidder.PlacementId == 0 { + return &errortypes.BadInput{ + Message: "placementId should not be 0.", + } + } + imp.TagID = strconv.Itoa(defaultImpExt.Bidder.PlacementId) + teadsImpExt := &teadsImpExtension{ + KV: teadsKV{ + PlacementId: defaultImpExt.Bidder.PlacementId, + }, + } + if extJson, err := json.Marshal(teadsImpExt); err != nil { + return &errortypes.BadInput{ + Message: "Error stringify Imp.Ext object", + } + } else { + imp.Ext = extJson + } + } + return nil +} + +// Builds enpoint url based on adapter-specific pub settings from imp.ext +func (a *adapter) buildEndpointURL() (string, error) { + endpointParams := macros.EndpointTemplateParams{} + host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams) + + if err != nil { + return "", &errortypes.BadInput{ + Message: "Unable to parse endpoint url template: " + err.Error(), + } + } + + thisURI, err := url.Parse(host) + if err != nil { + return "", &errortypes.BadInput{ + Message: "Malformed URL: " + err.Error(), + } + } + + return thisURI.String(), nil +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, _ *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(response) { + return nil, nil + } + + err := adapters.CheckResponseStatusCodeForErrors(response) + if err != nil { + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidderResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid)) + + for _, sb := range bidResp.SeatBid { + for i := 0; i < len(sb.Bid); i++ { + bid := sb.Bid[i] + + bidExtTeads, err := getTeadsRendererFromBidExt(bid.Ext) + if err != nil { + return nil, err + } + bidType, err := getMediaTypeForImp(bid.ImpID, internalRequest.Imp) + if err != nil { + return nil, err + } + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + RendererName: bidExtTeads.Prebid.Meta.RendererName, + RendererVersion: bidExtTeads.Prebid.Meta.RendererVersion, + }, + BidType: bidType, + }) + } + } + if bidResp.Cur != "" { + bidderResponse.Currency = bidResp.Cur + } + return bidderResponse, nil +} + +func getTeadsRendererFromBidExt(ext json.RawMessage) (*teadsBidExt, []error) { + var bidExtTeads teadsBidExt + if err := json.Unmarshal(ext, &bidExtTeads); err != nil { + return nil, []error{err} + } + if bidExtTeads.Prebid.Meta.RendererName == "" { + return nil, []error{&errortypes.BadInput{ + Message: "RendererName should not be empty if present", + }} + } + if bidExtTeads.Prebid.Meta.RendererVersion == "" { + return nil, []error{&errortypes.BadInput{ + Message: "RendererVersion should not be empty if present", + }} + } + return &bidExtTeads, nil +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, []error) { + for _, imp := range imps { + if imp.ID == impID { + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + return openrtb_ext.BidTypeBanner, nil + } + } + return openrtb_ext.BidType(""), []error{&errortypes.BadInput{ + Message: "Imp ids were not equals", + }} +} diff --git a/adapters/teads/teads_test.go b/adapters/teads/teads_test.go new file mode 100644 index 00000000000..791ce7aed6f --- /dev/null +++ b/adapters/teads/teads_test.go @@ -0,0 +1,27 @@ +package teads + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderTeads, config.Adapter{ + Endpoint: "https://a.teads.tv/prebid-server/bid-request"}, config.Server{ExternalUrl: "https://a.teads.tv/prebid-server/bid-request", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "teadstest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderTeads, config.Adapter{ + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "https://a.teads.tv/prebid-server/bid-request", GvlID: 1, DataCenter: "2"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/teads/teadstest/exemplary/simple-banner-with-format.json b/adapters/teads/teadstest/exemplary/simple-banner-with-format.json new file mode 100644 index 00000000000..b2677e6faba --- /dev/null +++ b/adapters/teads/teadstest/exemplary/simple-banner-with-format.json @@ -0,0 +1,168 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://a.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "banner": { + "w": 300, + "h": 250, + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/exemplary/simple-banner.json b/adapters/teads/teadstest/exemplary/simple-banner.json new file mode 100644 index 00000000000..2d9be7c7368 --- /dev/null +++ b/adapters/teads/teadstest/exemplary/simple-banner.json @@ -0,0 +1,158 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://a.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/exemplary/simple-video.json b/adapters/teads/teadstest/exemplary/simple-video.json new file mode 100644 index 00000000000..814569a47e1 --- /dev/null +++ b/adapters/teads/teadstest/exemplary/simple-video.json @@ -0,0 +1,184 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://a.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json b/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json new file mode 100644 index 00000000000..384e72fb537 --- /dev/null +++ b/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json @@ -0,0 +1,152 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://a.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "cur": "EUR", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "does-not-match", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Imp ids were not equals", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/currency-empty-string.json b/adapters/teads/teadstest/supplemental/currency-empty-string.json new file mode 100644 index 00000000000..5f0d700b14b --- /dev/null +++ b/adapters/teads/teadstest/supplemental/currency-empty-string.json @@ -0,0 +1,185 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://a.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "cur": "EUR", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/no-impression-response.json b/adapters/teads/teadstest/supplemental/no-impression-response.json new file mode 100644 index 00000000000..814569a47e1 --- /dev/null +++ b/adapters/teads/teadstest/supplemental/no-impression-response.json @@ -0,0 +1,184 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://a.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/no-impression.json b/adapters/teads/teadstest/supplemental/no-impression.json new file mode 100644 index 00000000000..7b1cdceb9e1 --- /dev/null +++ b/adapters/teads/teadstest/supplemental/no-impression.json @@ -0,0 +1,12 @@ +{ + "mockBidRequest": { + "id": "test-request-id" + }, + + "expectedMakeRequestsErrors": [ + { + "value": "No impression in the bid request", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/no-placementId.json b/adapters/teads/teadstest/supplemental/no-placementId.json new file mode 100644 index 00000000000..e343bc82a34 --- /dev/null +++ b/adapters/teads/teadstest/supplemental/no-placementId.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "placementId should not be 0.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/renderer-name-empty.json b/adapters/teads/teadstest/supplemental/renderer-name-empty.json new file mode 100644 index 00000000000..da4ee9a5094 --- /dev/null +++ b/adapters/teads/teadstest/supplemental/renderer-name-empty.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://a.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3 + ], + "w": 940, + "h": 560 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "RendererName should not be empty if present", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/renderer-version-empty.json b/adapters/teads/teadstest/supplemental/renderer-version-empty.json new file mode 100644 index 00000000000..e9e7b278dcb --- /dev/null +++ b/adapters/teads/teadstest/supplemental/renderer-version-empty.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://a.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3 + ], + "w": 940, + "h": 560 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "RendererVersion should not be empty if present", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/status-400.json b/adapters/teads/teadstest/supplemental/status-400.json new file mode 100644 index 00000000000..cd9fafff0a1 --- /dev/null +++ b/adapters/teads/teadstest/supplemental/status-400.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://a.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "kv": { + "placementId": 1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/status-500.json b/adapters/teads/teadstest/supplemental/status-500.json new file mode 100644 index 00000000000..337d4754006 --- /dev/null +++ b/adapters/teads/teadstest/supplemental/status-500.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://a.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "kv": { + "placementId": 1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 5d06ba9ba43..d8ab16c1d61 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -159,6 +159,7 @@ import ( "github.com/prebid/prebid-server/adapters/suntContent" "github.com/prebid/prebid-server/adapters/taboola" "github.com/prebid/prebid-server/adapters/tappx" + "github.com/prebid/prebid-server/adapters/teads" "github.com/prebid/prebid-server/adapters/telaria" "github.com/prebid/prebid-server/adapters/tpmn" "github.com/prebid/prebid-server/adapters/trafficgate" @@ -363,6 +364,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderSynacormedia: imds.Builder, openrtb_ext.BidderTaboola: taboola.Builder, openrtb_ext.BidderTappx: tappx.Builder, + openrtb_ext.BidderTeads: teads.Builder, openrtb_ext.BidderTelaria: telaria.Builder, openrtb_ext.BidderTpmn: tpmn.Builder, openrtb_ext.BidderTrafficGate: trafficgate.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index fd5ae176ab1..635f17cd58f 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -191,6 +191,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderSynacormedia, BidderTaboola, BidderTappx, + BidderTeads, BidderTelaria, BidderTpmn, BidderTrafficGate, @@ -481,6 +482,7 @@ const ( BidderSynacormedia BidderName = "synacormedia" BidderTaboola BidderName = "taboola" BidderTappx BidderName = "tappx" + BidderTeads BidderName = "teads" BidderTelaria BidderName = "telaria" BidderTpmn BidderName = "tpmn" BidderTrafficGate BidderName = "trafficgate" diff --git a/openrtb_ext/imp_teads.go b/openrtb_ext/imp_teads.go new file mode 100644 index 00000000000..50d11ae9192 --- /dev/null +++ b/openrtb_ext/imp_teads.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpTeads struct { + PlacementID string `json:"placementId"` +} diff --git a/static/bidder-info/teads.yaml b/static/bidder-info/teads.yaml new file mode 100644 index 00000000000..5dc428ddf7b --- /dev/null +++ b/static/bidder-info/teads.yaml @@ -0,0 +1,9 @@ +endpoint: "https://a.teads.tv/prebid-server/bid-request" +maintainer: + email: "support-sdk@teads.com" +gvlVendorID: 132 +capabilities: + app: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-params/teads.json b/static/bidder-params/teads.json new file mode 100644 index 00000000000..2b3f47d8bc2 --- /dev/null +++ b/static/bidder-params/teads.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Teads Adapter Params", + "description": "A schema which validates params accepted by the Teads adapter", + + "type": "object", + "properties": { + "placementId": { + "type": "integer", + "description": "The placement id.", + "minimum": 1 + } + }, + "required": ["placementId"] + } \ No newline at end of file From dff9b3b581e66dce0ed32a3d3d3cdf83771cb984 Mon Sep 17 00:00:00 2001 From: vrtcal-dev <50931150+vrtcal-dev@users.noreply.github.com> Date: Mon, 16 Oct 2023 05:59:02 -0500 Subject: [PATCH 044/138] Updated User Sync Support and GPP Support Flag (#3181) Co-authored-by: Ubuntu --- static/bidder-info/vrtcal.yaml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/static/bidder-info/vrtcal.yaml b/static/bidder-info/vrtcal.yaml index 2aeb292a7ed..1ce005cd223 100644 --- a/static/bidder-info/vrtcal.yaml +++ b/static/bidder-info/vrtcal.yaml @@ -14,7 +14,11 @@ capabilities: - video - native userSync: - # vrtcal supports user syncing, but requires configuration by the host. contact this - # bidder directly at the email address in this file to ask about enabling user sync. - supports: - - redirect + iframe: + url: "https://usync.vrtcal.com/i?ssp=1804&synctype=iframe&us_privacy={{.USPrivacy}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&surl={{.RedirectURL}}" + userMacro: "$$VRTCALUSER$$" + redirect: + url: "https://usync.vrtcal.com/i?ssp=1804&synctype=redirect&us_privacy={{.USPrivacy}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&surl={{.RedirectURL}}" + userMacro: "$$VRTCALUSER$$" +openrtb: + gpp-supported: true From 987e640abf1bd069f7da0825c6622b30ea2d5c9e Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Mon, 16 Oct 2023 16:40:50 +0530 Subject: [PATCH 045/138] make event endpoint case insensitive (#3199) --- endpoints/events/event.go | 8 +++++++- endpoints/events/event_test.go | 13 +++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/endpoints/events/event.go b/endpoints/events/event.go index d5cf89b2ef5..ffcb4e006e1 100644 --- a/endpoints/events/event.go +++ b/endpoints/events/event.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/prebid/prebid-server/openrtb_ext" "net/http" "net/url" "strconv" @@ -188,7 +189,12 @@ func ParseEventRequest(r *http.Request) (*analytics.EventRequest, []error) { } // Bidder - event.Bidder = r.URL.Query().Get(BidderParameter) + bidderName := r.URL.Query().Get(BidderParameter) + if normalisedBidderName, ok := openrtb_ext.NormalizeBidderName(bidderName); ok { + bidderName = normalisedBidderName.String() + } + + event.Bidder = bidderName return event, errs } diff --git a/endpoints/events/event_test.go b/endpoints/events/event_test.go index d721187b6ff..5fc115786fe 100644 --- a/endpoints/events/event_test.go +++ b/endpoints/events/event_test.go @@ -651,6 +651,19 @@ func TestShouldParseEventCorrectly(t *testing.T) { Analytics: analytics.Enabled, }, }, + "case insensitive bidder name": { + req: httptest.NewRequest("GET", "/event?t=win&b=bidId&f=b&ts=1000&x=1&a=accountId&bidder=RubiCon&int=intType", strings.NewReader("")), + expected: &analytics.EventRequest{ + Type: analytics.Win, + BidID: "bidId", + Timestamp: 1000, + Bidder: "rubicon", + AccountID: "", + Format: analytics.Blank, + Analytics: analytics.Enabled, + Integration: "intType", + }, + }, } for name, test := range tests { From 05e06bb1b22f9a957813ec77fdbbe29947d5aa25 Mon Sep 17 00:00:00 2001 From: lm-ved <105272141+lm-ved@users.noreply.github.com> Date: Mon, 16 Oct 2023 16:43:38 +0530 Subject: [PATCH 046/138] New Adapter: LemmaDigital (#3157) Co-authored-by: Tushar Pathare --- adapters/lemmadigital/lemmadigital.go | 114 ++++++++++++++ adapters/lemmadigital/lemmadigital_test.go | 20 +++ .../lemmadigitaltest/exemplary/banner.json | 110 +++++++++++++ .../lemmadigitaltest/exemplary/multi-imp.json | 144 ++++++++++++++++++ .../lemmadigitaltest/exemplary/video.json | 89 +++++++++++ .../supplemental/empty-imps.json | 18 +++ .../supplemental/empty-seatbid-array.json | 101 ++++++++++++ .../invalid-ld-ext-bidder-object.json | 48 ++++++ .../supplemental/invalid-ld-ext-object.json | 42 +++++ .../supplemental/invalid-response.json | 62 ++++++++ .../supplemental/status-code-bad-request.json | 62 ++++++++ .../supplemental/status-code-no-content.json | 59 +++++++ .../supplemental/status-code-other-error.json | 62 ++++++++ adapters/lemmadigital/params_test.go | 59 +++++++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_lemmadigital.go | 6 + static/bidder-info/lemmadigital.yaml | 14 ++ static/bidder-params/lemmadigital.json | 19 +++ 19 files changed, 1033 insertions(+) create mode 100644 adapters/lemmadigital/lemmadigital.go create mode 100644 adapters/lemmadigital/lemmadigital_test.go create mode 100644 adapters/lemmadigital/lemmadigitaltest/exemplary/banner.json create mode 100644 adapters/lemmadigital/lemmadigitaltest/exemplary/multi-imp.json create mode 100644 adapters/lemmadigital/lemmadigitaltest/exemplary/video.json create mode 100644 adapters/lemmadigital/lemmadigitaltest/supplemental/empty-imps.json create mode 100644 adapters/lemmadigital/lemmadigitaltest/supplemental/empty-seatbid-array.json create mode 100644 adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-bidder-object.json create mode 100644 adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-object.json create mode 100644 adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-response.json create mode 100644 adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-bad-request.json create mode 100644 adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-no-content.json create mode 100644 adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-other-error.json create mode 100644 adapters/lemmadigital/params_test.go create mode 100644 openrtb_ext/imp_lemmadigital.go create mode 100644 static/bidder-info/lemmadigital.yaml create mode 100644 static/bidder-params/lemmadigital.json diff --git a/adapters/lemmadigital/lemmadigital.go b/adapters/lemmadigital/lemmadigital.go new file mode 100644 index 00000000000..3b77de52984 --- /dev/null +++ b/adapters/lemmadigital/lemmadigital.go @@ -0,0 +1,114 @@ +package lemmadigital + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "text/template" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpoint *template.Template +} + +// Builder builds a new instance of the Lemmadigital adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpoint: template, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if len(request.Imp) == 0 { + return nil, []error{errors.New("Impression array should not be empty")} + } + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Invalid imp.ext for impression index %d. Error Infomation: %s", 0, err.Error()), + }} + } + + var impExt openrtb_ext.ImpExtLemmaDigital + if err := json.Unmarshal(bidderExt.Bidder, &impExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Invalid imp.ext.bidder for impression index %d. Error Infomation: %s", 0, err.Error()), + }} + } + + endpoint, err := a.buildEndpointURL(impExt) + if err != nil { + return nil, []error{err} + } + + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: endpoint, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidType := openrtb_ext.BidTypeBanner + if nil != request.Imp[0].Video { + bidType = openrtb_ext.BidTypeVideo + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + if len(response.Cur) > 0 { + bidResponse.Currency = response.Cur + } + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + break + } + + return bidResponse, nil +} + +func (a *adapter) buildEndpointURL(params openrtb_ext.ImpExtLemmaDigital) (string, error) { + endpointParams := macros.EndpointTemplateParams{PublisherID: strconv.Itoa(params.PublisherId), + AdUnit: strconv.Itoa(params.AdId)} + return macros.ResolveMacros(a.endpoint, endpointParams) +} diff --git a/adapters/lemmadigital/lemmadigital_test.go b/adapters/lemmadigital/lemmadigital_test.go new file mode 100644 index 00000000000..0372fa81d6b --- /dev/null +++ b/adapters/lemmadigital/lemmadigital_test.go @@ -0,0 +1,20 @@ +package lemmadigital + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderLemmadigital, config.Adapter{ + Endpoint: "https://sg.ads.lemmatechnologies.com/lemma/servad?pid={{.PublisherID}}&aid={{.AdUnit}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "lemmadigitaltest", bidder) +} diff --git a/adapters/lemmadigital/lemmadigitaltest/exemplary/banner.json b/adapters/lemmadigital/lemmadigitaltest/exemplary/banner.json new file mode 100644 index 00000000000..a478380e394 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/exemplary/banner.json @@ -0,0 +1,110 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 1920, + "h": 1080 + }], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "bidfloor": 0.1 + }], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 1920, + "h": 1080 + }], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "bidfloor": 0.1 + }], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "1", + "bid": [{ + "id": "1239875642389471056", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "1", + "adm": "some-test-ad", + "adomain": ["lemmadigital.com"], + "crid": "1", + "h": 1080, + "w": 1920, + "dealid": "test_deal" + }] + }], + "bidid": "1239875642389471056", + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1239875642389471056", + "impid": "test-imp-id", + "price": 0.5, + "adid": "1", + "adm": "some-test-ad", + "adomain": ["lemmadigital.com"], + "crid": "1", + "w": 1920, + "h": 1080, + "dealid": "test_deal" + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/exemplary/multi-imp.json b/adapters/lemmadigital/lemmadigitaltest/exemplary/multi-imp.json new file mode 100644 index 00000000000..e051b54ff95 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/exemplary/multi-imp.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 1920, + "h": 1080 + }], + "h": 1080, + "w": 1920 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "bidfloor": 0.1 + }, { + "id": "test-imp-id-2", + "banner": { + "format": [{ + "w": 1080, + "h": 1920 + }], + "h": 1920, + "w": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "bidfloor": 0.12 + }], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 1920, + "h": 1080 + }], + "h": 1080, + "w": 1920 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "bidfloor": 0.1 + }, { + "id": "test-imp-id-2", + "banner": { + "format": [{ + "w": 1080, + "h": 1920 + }], + "h": 1920, + "w": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "bidfloor": 0.12 + }], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "1", + "bid": [{ + "id": "1239875642389471056", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "1", + "adm": "some-test-ad", + "adomain": ["lemmadigital.com"], + "crid": "1", + "h": 1080, + "w": 1920, + "dealid": "test_deal" + }] + }], + "bidid": "1239875642389471056", + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1239875642389471056", + "impid": "test-imp-id", + "price": 0.5, + "adid": "1", + "adm": "some-test-ad", + "adomain": ["lemmadigital.com"], + "crid": "1", + "w": 1920, + "h": 1080, + "dealid": "test_deal" + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/exemplary/video.json b/adapters/lemmadigital/lemmadigitaltest/exemplary/video.json new file mode 100644 index 00000000000..63bab75b674 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/exemplary/video.json @@ -0,0 +1,89 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-video", + "seatbid": [{ + "seat": "1", + "bid": [{ + "id": "1239875642389471056", + "impid": "test-imp-id-video", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_video", + "h": 1080, + "w": 1920 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1239875642389471056", + "impid": "test-imp-id-video", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_video", + "w": 1920, + "h": 1080 + }, + "type": "video" + }] + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-imps.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-imps.json new file mode 100644 index 00000000000..e13cdcb480a --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-imps.json @@ -0,0 +1,18 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [], + "expectedBidResponses": [], + "expectedMakeRequestsErrors": [{ + "value": "Impression array should not be empty", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-seatbid-array.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-seatbid-array.json new file mode 100644 index 00000000000..c88e0fa7861 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-seatbid-array.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.ld.test", + "cat": [ + "IAB-1" + ], + "domain": "ld.com", + "id": "1", + "name": "LD Test", + "publisher": { + "id": "1" + } + }, + "device": { + "dnt": 0, + "ip": "0.0.0.0", + "language": "en", + "ua": "user-agent" + }, + "id": "test-request-id-video", + "imp": [{ + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "id": "test-imp-id-video", + "video": { + "h": 1080, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 1 + ], + "w": 1920 + } + }], + "tmax": 1000 + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "app": { + "bundle": "com.ld.test", + "cat": [ + "IAB-1" + ], + "domain": "ld.com", + "id": "1", + "name": "LD Test", + "publisher": { + "id": "1" + } + }, + "device": { + "dnt": 0, + "ip": "0.0.0.0", + "language": "en", + "ua": "user-agent" + }, + "id": "test-request-id-video", + "imp": [{ + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "id": "test-imp-id-video", + "video": { + "h": 1080, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 1 + ], + "w": 1920 + } + }], + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-1", + "seatbid": [], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [] +}] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-bidder-object.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-bidder-object.json new file mode 100644 index 00000000000..ba46d76b598 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-bidder-object.json @@ -0,0 +1,48 @@ +{ + "expectedMakeRequestsErrors": [{ + "value": "Invalid imp.ext.bidder for impression index 0. Error Infomation: json: cannot unmarshal string into Go struct field ImpExtLemmaDigital.pid of type int", + "comparison": "literal" + }], + "mockBidRequest": { + "app": { + "bundle": "com.ld.test", + "cat": [ + "IAB-1" + ], + "domain": "ld.com", + "id": "1", + "name": "LD Test", + "publisher": { + "id": "1" + } + }, + "device": { + "dnt": 0, + "ip": "0.0.0.0", + "language": "en", + "ua": "user-agent" + }, + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "h": 1080, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 1 + ], + "w": 1920 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": "1" + } + } + }], + "tmax": 1000 + }, + "httpCalls": [] + } \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-object.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-object.json new file mode 100644 index 00000000000..4d3d795c185 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-object.json @@ -0,0 +1,42 @@ +{ + "expectedMakeRequestsErrors": [{ + "value": "Invalid imp.ext for impression index 0. Error Infomation: unexpected end of JSON input", + "comparison": "literal" + }], + "mockBidRequest": { + "app": { + "bundle": "com.ld.test", + "cat": [ + "IAB-1" + ], + "domain": "ld.com", + "id": "1", + "name": "LD Test", + "publisher": { + "id": "1" + } + }, + "device": { + "dnt": 0, + "ip": "0.0.0.0", + "language": "en", + "ua": "user-agent" + }, + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "h": 1080, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 1 + ], + "w": 1920 + } + }], + "tmax": 1000 + }, + "httpCalls": [] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-response.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-response.json new file mode 100644 index 00000000000..036d35fb345 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-response.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + }], + "expectedMakeBidsErrors": [{ + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-bad-request.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..37ebea4b7be --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-bad-request.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 400 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{ + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-no-content.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..7c4813df43a --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-no-content.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 204 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-other-error.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..047dc4efd83 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-other-error.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 503 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{ + "value": "Unexpected status code: 503. Run with request.debug = 1 for more info", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/params_test.go b/adapters/lemmadigital/params_test.go new file mode 100644 index 00000000000..57bc7d83e79 --- /dev/null +++ b/adapters/lemmadigital/params_test.go @@ -0,0 +1,59 @@ +package lemmadigital + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// Tests for static/bidder-params/lemmadigital.json + +// Tests whether the schema supports the intended params. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schema. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderLemmadigital, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected params: %s \n Error: %s", validParam, err) + } + } +} + +// Tests whether the schema rejects unsupported imp.ext fields. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schema. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderLemmadigital, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed invalid/unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"pid":1, "aid": 1}`, + `{"pid":2147483647, "aid": 2147483647}`, +} + +var invalidParams = []string{ + ``, + `null`, + `false`, + `0`, + `0.0`, + `[]`, + `{}`, + `{"pid":1}`, + `{"aid":1}`, + `{"pid":"1","aid":1}`, + `{"pid":1.0,"aid":"1"}`, + `{"pid":"1","aid":"1"}`, + `{"pid":false,"aid":true}`, +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index d8ab16c1d61..25630fed414 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -103,6 +103,7 @@ import ( "github.com/prebid/prebid-server/adapters/kidoz" "github.com/prebid/prebid-server/adapters/kiviads" "github.com/prebid/prebid-server/adapters/krushmedia" + "github.com/prebid/prebid-server/adapters/lemmadigital" "github.com/prebid/prebid-server/adapters/liftoff" "github.com/prebid/prebid-server/adapters/limelightDigital" lmkiviads "github.com/prebid/prebid-server/adapters/lm_kiviads" @@ -304,6 +305,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderKiviads: kiviads.Builder, openrtb_ext.BidderLmKiviads: lmkiviads.Builder, openrtb_ext.BidderKrushmedia: krushmedia.Builder, + openrtb_ext.BidderLemmadigital: lemmadigital.Builder, openrtb_ext.BidderLiftoff: liftoff.Builder, openrtb_ext.BidderLimelightDigital: limelightDigital.Builder, openrtb_ext.BidderLockerDome: lockerdome.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 635f17cd58f..744c7fad3fe 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -131,6 +131,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderKiviads, BidderLmKiviads, BidderKrushmedia, + BidderLemmadigital, BidderLiftoff, BidderLimelightDigital, BidderLockerDome, @@ -422,6 +423,7 @@ const ( BidderKiviads BidderName = "kiviads" BidderLmKiviads BidderName = "lm_kiviads" BidderKrushmedia BidderName = "krushmedia" + BidderLemmadigital BidderName = "lemmadigital" BidderLiftoff BidderName = "liftoff" BidderLimelightDigital BidderName = "limelightDigital" BidderLockerDome BidderName = "lockerdome" diff --git a/openrtb_ext/imp_lemmadigital.go b/openrtb_ext/imp_lemmadigital.go new file mode 100644 index 00000000000..c691dd173d9 --- /dev/null +++ b/openrtb_ext/imp_lemmadigital.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtLemmaDigital struct { + PublisherId int `json:"pid"` + AdId int `json:"aid"` +} diff --git a/static/bidder-info/lemmadigital.yaml b/static/bidder-info/lemmadigital.yaml new file mode 100644 index 00000000000..535c91ffa77 --- /dev/null +++ b/static/bidder-info/lemmadigital.yaml @@ -0,0 +1,14 @@ +endpoint: "https://sg.ads.lemmatechnologies.com/lemma/servad?pid={{.PublisherID}}&aid={{.AdUnit}}" +maintainer: + email: support@lemmatechnologies.com +endpointCompression: gzip +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-params/lemmadigital.json b/static/bidder-params/lemmadigital.json new file mode 100644 index 00000000000..be4a89edd7a --- /dev/null +++ b/static/bidder-params/lemmadigital.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Lemma Adapter Params", + "description": "A schema which validates params accepted by the Lemma adapter", + "type": "object", + + "properties": { + "pid": { + "type": "integer", + "description": "Publisher ID" + }, + "aid": { + "type": "integer", + "description": "Ad ID" + } + }, + + "required": ["pid", "aid"] +} \ No newline at end of file From 2dbf6719898e4a2f041db672d4b25f09310b20e0 Mon Sep 17 00:00:00 2001 From: Irakli Gotsiridze Date: Mon, 16 Oct 2023 15:06:51 +0300 Subject: [PATCH 047/138] minduration non-required (#3185) --- adapters/sovrn/sovrn.go | 1 - .../sovrn/sovrntest/video/simple-video.json | 2 - .../videosupplemental/no-minduration.json | 49 ------------------- 3 files changed, 52 deletions(-) delete mode 100644 adapters/sovrn/sovrntest/videosupplemental/no-minduration.json diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index 405e5ebf763..15b07800b78 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -97,7 +97,6 @@ func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt video := imp.Video if video != nil { if video.MIMEs == nil || - video.MinDuration == 0 || video.MaxDuration == 0 || video.Protocols == nil { errs = append(errs, &errortypes.BadInput{ diff --git a/adapters/sovrn/sovrntest/video/simple-video.json b/adapters/sovrn/sovrntest/video/simple-video.json index 9cfeef5e11d..fae6778a015 100644 --- a/adapters/sovrn/sovrntest/video/simple-video.json +++ b/adapters/sovrn/sovrntest/video/simple-video.json @@ -10,7 +10,6 @@ "video/3gpp", "video/x-ms-wmv" ], - "minduration": 5, "maxduration": 30, "protocols": [ 4, @@ -79,7 +78,6 @@ "video/3gpp", "video/x-ms-wmv" ], - "minduration": 5, "maxduration": 30, "protocols": [ 4, diff --git a/adapters/sovrn/sovrntest/videosupplemental/no-minduration.json b/adapters/sovrn/sovrntest/videosupplemental/no-minduration.json deleted file mode 100644 index 88703ddadc4..00000000000 --- a/adapters/sovrn/sovrntest/videosupplemental/no-minduration.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": [ - "video/mp4", - "video/3gpp", - "video/x-ms-wmv" - ], - "maxduration": 30, - "protocols": [ - 4, - 5, - 6, - 8 - ] - }, - "ext": { - "bidder": { - "tagid": "123456" - } - } - } - ], - "device": { - "ua": "test-user-agent", - "ip": "123.123.123.123", - "language": "en", - "dnt": 0 - }, - "site": { - "domain": "www.publisher.com", - "page": "http://www.publisher.com/awesome/site" - }, - "user": { - "buyeruid": "test_reader_id" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "Missing required video parameter", - "comparison": "literal" - } - ], - "httpCalls": [] -} From c45ab815c19a7e915331c13f9ffa08bd89371ac5 Mon Sep 17 00:00:00 2001 From: Arne Schulz Date: Mon, 16 Oct 2023 14:08:57 +0200 Subject: [PATCH 048/138] add supportCORS to orbidder user sync config (#3193) --- static/bidder-info/orbidder.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/static/bidder-info/orbidder.yaml b/static/bidder-info/orbidder.yaml index 74348c75d7f..c42fb91de44 100644 --- a/static/bidder-info/orbidder.yaml +++ b/static/bidder-info/orbidder.yaml @@ -11,6 +11,7 @@ capabilities: - banner - native userSync: + supportCors: true redirect: url: "https://orbidder.otto.de/pbs-usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&redirect={{.RedirectURL}}" userMacro: "[ODN_ID]" \ No newline at end of file From 9d216fb8a04bff546bf403899135d4047050262d Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Mon, 16 Oct 2023 18:41:46 +0530 Subject: [PATCH 049/138] Adapter Name Case Insensitive: /vtrack endpoint (#3196) authored by @onkarvhanumante --- endpoints/events/vtrack.go | 41 ++++--- endpoints/events/vtrack_test.go | 209 ++++++++++++++++++++++++-------- 2 files changed, 182 insertions(+), 68 deletions(-) diff --git a/endpoints/events/vtrack.go b/endpoints/events/vtrack.go index d320fdc6989..e0bf3fd4be8 100644 --- a/endpoints/events/vtrack.go +++ b/endpoints/events/vtrack.go @@ -16,6 +16,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests" ) @@ -27,12 +28,15 @@ const ( ImpressionOpenTag = "" ) +type normalizeBidderName func(name string) (openrtb_ext.BidderName, bool) + type vtrackEndpoint struct { - Cfg *config.Configuration - Accounts stored_requests.AccountFetcher - BidderInfos config.BidderInfos - Cache prebid_cache_client.Client - MetricsEngine metrics.MetricsEngine + Cfg *config.Configuration + Accounts stored_requests.AccountFetcher + BidderInfos config.BidderInfos + Cache prebid_cache_client.Client + MetricsEngine metrics.MetricsEngine + normalizeBidderName normalizeBidderName } type BidCacheRequest struct { @@ -49,11 +53,12 @@ type CacheObject struct { func NewVTrackEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, cache prebid_cache_client.Client, bidderInfos config.BidderInfos, me metrics.MetricsEngine) httprouter.Handle { vte := &vtrackEndpoint{ - Cfg: cfg, - Accounts: accounts, - BidderInfos: bidderInfos, - Cache: cache, - MetricsEngine: me, + Cfg: cfg, + Accounts: accounts, + BidderInfos: bidderInfos, + Cache: cache, + MetricsEngine: me, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } return vte.Handle @@ -203,7 +208,7 @@ func ParseVTrackRequest(httpRequest *http.Request, maxRequestSize int64) (req *B // handleVTrackRequest handles a VTrack request func (v *vtrackEndpoint) handleVTrackRequest(ctx context.Context, req *BidCacheRequest, account *config.Account, integration string) (*BidCacheResponse, []error) { - biddersAllowingVastUpdate := getBiddersAllowingVastUpdate(req, &v.BidderInfos, v.Cfg.VTrack.AllowUnknownBidder) + biddersAllowingVastUpdate := getBiddersAllowingVastUpdate(req, &v.BidderInfos, v.Cfg.VTrack.AllowUnknownBidder, v.normalizeBidderName) // cache data r, errs := v.cachePutObjects(ctx, req, biddersAllowingVastUpdate, account.ID, integration) @@ -251,11 +256,11 @@ func (v *vtrackEndpoint) cachePutObjects(ctx context.Context, req *BidCacheReque } // getBiddersAllowingVastUpdate returns a list of bidders that allow VAST XML modification -func getBiddersAllowingVastUpdate(req *BidCacheRequest, bidderInfos *config.BidderInfos, allowUnknownBidder bool) map[string]struct{} { +func getBiddersAllowingVastUpdate(req *BidCacheRequest, bidderInfos *config.BidderInfos, allowUnknownBidder bool, normalizeBidderName normalizeBidderName) map[string]struct{} { bl := map[string]struct{}{} for _, bcr := range req.Puts { - if _, ok := bl[bcr.Bidder]; isAllowVastForBidder(bcr.Bidder, bidderInfos, allowUnknownBidder) && !ok { + if _, ok := bl[bcr.Bidder]; isAllowVastForBidder(bcr.Bidder, bidderInfos, allowUnknownBidder, normalizeBidderName) && !ok { bl[bcr.Bidder] = struct{}{} } } @@ -264,12 +269,14 @@ func getBiddersAllowingVastUpdate(req *BidCacheRequest, bidderInfos *config.Bidd } // isAllowVastForBidder checks if a bidder is active and allowed to modify vast xml data -func isAllowVastForBidder(bidder string, bidderInfos *config.BidderInfos, allowUnknownBidder bool) bool { +func isAllowVastForBidder(bidder string, bidderInfos *config.BidderInfos, allowUnknownBidder bool, normalizeBidderName normalizeBidderName) bool { //if bidder is active and isModifyingVastXmlAllowed is true // check if bidder is configured - if b, ok := (*bidderInfos)[bidder]; bidderInfos != nil && ok { - // check if bidder is enabled - return b.IsEnabled() && b.ModifyingVastXmlAllowed + if normalizedBidder, ok := normalizeBidderName(bidder); ok { + if b, ok := (*bidderInfos)[normalizedBidder.String()]; bidderInfos != nil && ok { + // check if bidder is enabled + return b.IsEnabled() && b.ModifyingVastXmlAllowed + } } return allowUnknownBidder diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index 2c40c9b41de..1e36a9e627f 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -13,6 +13,7 @@ import ( "testing" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests" "github.com/stretchr/testify/assert" @@ -28,15 +29,17 @@ const ( // Mock pbs cache client type vtrackMockCacheClient struct { - Fail bool - Error error - Uuids []string + Fail bool + Error error + Uuids []string + Values []prebid_cache_client.Cacheable } func (m *vtrackMockCacheClient) PutJson(ctx context.Context, values []prebid_cache_client.Cacheable) ([]string, []error) { if m.Fail { return []string{}, []error{m.Error} } + m.Values = values return m.Uuids, []error{} } func (m *vtrackMockCacheClient) GetExtCacheData() (scheme string, host string, path string) { @@ -64,10 +67,11 @@ func TestShouldRespondWithBadRequestWhenAccountParameterIsMissing(t *testing.T) recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -105,10 +109,11 @@ func TestShouldRespondWithBadRequestWhenRequestBodyIsEmpty(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -146,10 +151,11 @@ func TestShouldRespondWithBadRequestWhenRequestBodyIsInvalid(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -190,10 +196,11 @@ func TestShouldRespondWithBadRequestWhenBidIdIsMissing(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -242,10 +249,11 @@ func TestShouldRespondWithBadRequestWhenBidderIsMissing(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -291,10 +299,11 @@ func TestShouldRespondWithInternalServerErrorWhenPbsCacheClientFails(t *testing. recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -340,10 +349,11 @@ func TestShouldTolerateAccountNotFound(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -397,10 +407,11 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastNotAllowe recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: bidderInfos, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: bidderInfos, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -459,11 +470,15 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastAllowed(t recorder := httptest.NewRecorder() + var mockNormalizeBidderName normalizeBidderName = func(name string) (openrtb_ext.BidderName, bool) { + return openrtb_ext.BidderName(name), true + } e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: bidderInfos, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: bidderInfos, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: mockNormalizeBidderName, } // execute @@ -478,6 +493,95 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastAllowed(t assert.Equal(t, 200, recorder.Result().StatusCode, "Expected 200 when account is not found and request is valid") assert.Equal(t, "{\"responses\":[{\"uuid\":\"uuid1\"},{\"uuid\":\"uuid2\"}]}", string(d), "Expected 200 when account is found and request is valid") assert.Equal(t, "application/json", recorder.Header().Get("Content-Type")) + assert.Len(t, mockCacheClient.Values, 2) + assert.Contains(t, string(mockCacheClient.Values[0].Data), "bidder=bidder") + assert.Contains(t, string(mockCacheClient.Values[1].Data), "bidder=updatable_bidder") +} + +func TestShouldSendToCacheExpectedPutsAndUpdatableCaseSensitiveBiddersWhenBidderVastAllowed(t *testing.T) { + // mock pbs cache client + mockCacheClient := &vtrackMockCacheClient{ + Fail: false, + Uuids: []string{"uuid1", "uuid2"}, + } + + // mock AccountsFetcher + mockAccountsFetcher := &mockAccountsFetcher{ + Fail: false, + } + + // config + cfg := &config.Configuration{ + MaxRequestSize: maxSize, VTrack: config.VTrack{ + TimeoutMS: int64(2000), AllowUnknownBidder: false, + }, + AccountDefaults: config.Account{}, + } + cfg.MarshalAccountDefaults() + + // bidder info + bidderInfos := make(config.BidderInfos) + bidderInfos["appnexus"] = config.BidderInfo{ + Disabled: false, + ModifyingVastXmlAllowed: true, + } + + d, err := getVTrackRequestData(true, true) + assert.NoError(t, err) + + cacheReq := &BidCacheRequest{ + Puts: []prebid_cache_client.Cacheable{ + { + Type: prebid_cache_client.TypeXML, + BidID: "bidId1", + Bidder: "APPNEXUS", // case sensitive name + Data: d, + TTLSeconds: 3600, + Timestamp: 1000, + }, + { + Type: prebid_cache_client.TypeXML, + BidID: "bidId2", + Bidder: "ApPnExUs", // case sensitive name + Data: d, + TTLSeconds: 3600, + Timestamp: 1000, + }, + }, + } + buf := &bytes.Buffer{} + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + err = enc.Encode(cacheReq) + assert.NoError(t, err) + data := buf.String() + + req := httptest.NewRequest("POST", "/vtrack?a=events_enabled", strings.NewReader(data)) + + recorder := httptest.NewRecorder() + e := vtrackEndpoint{ + Cfg: cfg, + BidderInfos: bidderInfos, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, + } + + // execute + e.Handle(recorder, req, nil) + + d, err = io.ReadAll(recorder.Result().Body) + if err != nil { + t.Fatal(err) + } + + // validate + assert.Equal(t, 200, recorder.Result().StatusCode, "Expected 200 when account is not found and request is valid") + assert.Equal(t, "{\"responses\":[{\"uuid\":\"uuid1\"},{\"uuid\":\"uuid2\"}]}", string(d), "Expected 200 when account is found and request is valid") + assert.Equal(t, "application/json", recorder.Header().Get("Content-Type")) + assert.Len(t, mockCacheClient.Values, 2) + assert.Contains(t, string(mockCacheClient.Values[0].Data), "bidder=APPNEXUS") + assert.Contains(t, string(mockCacheClient.Values[1].Data), "bidder=ApPnExUs") } func TestShouldSendToCacheExpectedPutsAndUpdatableUnknownBiddersWhenUnknownBidderIsAllowed(t *testing.T) { @@ -515,10 +619,11 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableUnknownBiddersWhenUnknownBidde recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: bidderInfos, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: bidderInfos, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -571,10 +676,11 @@ func TestShouldReturnBadRequestWhenRequestExceedsMaxRequestSize(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: bidderInfos, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: bidderInfos, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -615,10 +721,11 @@ func TestShouldRespondWithInternalErrorPbsCacheIsNotConfigured(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: nil, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: nil, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute From 5b44d36066dccaa378ac6dba031964ea48a576cf Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Mon, 16 Oct 2023 18:42:52 +0530 Subject: [PATCH 050/138] Prometheus metrics: bidder name should be in lowercase (#3198) co-authored by @onkarvhanumante --- metrics/prometheus/preload.go | 2 +- metrics/prometheus/prometheus.go | 33 +++++++----- metrics/prometheus/prometheus_test.go | 73 +++++++++++++++------------ metrics/prometheus/type_conversion.go | 9 ++++ 4 files changed, 71 insertions(+), 46 deletions(-) diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go index 59f70cfb9fb..db40feabbd8 100644 --- a/metrics/prometheus/preload.go +++ b/metrics/prometheus/preload.go @@ -9,7 +9,7 @@ import ( func preloadLabelValues(m *Metrics, syncerKeys []string, moduleStageNames map[string][]string) { var ( adapterErrorValues = enumAsString(metrics.AdapterErrors()) - adapterValues = enumAsString(openrtb_ext.CoreBidderNames()) + adapterValues = enumAsLowerCaseString(openrtb_ext.CoreBidderNames()) bidTypeValues = []string{markupDeliveryAdm, markupDeliveryNurl} boolValues = boolValuesAsString() cacheResultValues = enumAsString(metrics.CacheResults()) diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 30fb6c5f774..bd63997f0d2 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -3,6 +3,7 @@ package prometheusmetrics import ( "fmt" "strconv" + "strings" "time" "github.com/prebid/prebid-server/config" @@ -762,15 +763,16 @@ func (m *Metrics) RecordStoredDataError(labels metrics.StoredDataLabels) { } func (m *Metrics) RecordAdapterRequest(labels metrics.AdapterLabels) { + lowerCasedAdapter := strings.ToLower(string(labels.Adapter)) m.adapterRequests.With(prometheus.Labels{ - adapterLabel: string(labels.Adapter), + adapterLabel: lowerCasedAdapter, cookieLabel: string(labels.CookieFlag), hasBidsLabel: strconv.FormatBool(labels.AdapterBids == metrics.AdapterBidPresent), }).Inc() for err := range labels.AdapterErrors { m.adapterErrors.With(prometheus.Labels{ - adapterLabel: string(labels.Adapter), + adapterLabel: lowerCasedAdapter, adapterErrorLabel: string(err), }).Inc() } @@ -779,22 +781,23 @@ func (m *Metrics) RecordAdapterRequest(labels metrics.AdapterLabels) { // Keeps track of created and reused connections to adapter bidders and the time from the // connection request, to the connection creation, or reuse from the pool across all engines func (m *Metrics) RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) { + lowerCasedAdapterName := strings.ToLower(string(adapterName)) if m.metricsDisabled.AdapterConnectionMetrics { return } if connWasReused { m.adapterReusedConnections.With(prometheus.Labels{ - adapterLabel: string(adapterName), + adapterLabel: lowerCasedAdapterName, }).Inc() } else { m.adapterCreatedConnections.With(prometheus.Labels{ - adapterLabel: string(adapterName), + adapterLabel: lowerCasedAdapterName, }).Inc() } m.adapterConnectionWaitTime.With(prometheus.Labels{ - adapterLabel: string(adapterName), + adapterLabel: lowerCasedAdapterName, }).Observe(connWaitTime.Seconds()) } @@ -812,7 +815,7 @@ func (m *Metrics) RecordBidderServerResponseTime(bidderServerResponseTime time.D func (m *Metrics) RecordAdapterPanic(labels metrics.AdapterLabels) { m.adapterPanics.With(prometheus.Labels{ - adapterLabel: string(labels.Adapter), + adapterLabel: strings.ToLower(string(labels.Adapter)), }).Inc() } @@ -823,14 +826,14 @@ func (m *Metrics) RecordAdapterBidReceived(labels metrics.AdapterLabels, bidType } m.adapterBids.With(prometheus.Labels{ - adapterLabel: string(labels.Adapter), + adapterLabel: strings.ToLower(string(labels.Adapter)), markupDeliveryLabel: markupDelivery, }).Inc() } func (m *Metrics) RecordAdapterPrice(labels metrics.AdapterLabels, cpm float64) { m.adapterPrices.With(prometheus.Labels{ - adapterLabel: string(labels.Adapter), + adapterLabel: strings.ToLower(string(labels.Adapter)), }).Observe(cpm) } @@ -843,7 +846,7 @@ func (m *Metrics) RecordOverheadTime(overhead metrics.OverheadType, duration tim func (m *Metrics) RecordAdapterTime(labels metrics.AdapterLabels, length time.Duration) { if len(labels.AdapterErrors) == 0 { m.adapterRequestsTimer.With(prometheus.Labels{ - adapterLabel: string(labels.Adapter), + adapterLabel: strings.ToLower(string(labels.Adapter)), }).Observe(length.Seconds()) } } @@ -955,7 +958,7 @@ func (m *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.Bidder } m.adapterGDPRBlockedRequests.With(prometheus.Labels{ - adapterLabel: string(adapterName), + adapterLabel: strings.ToLower(string(adapterName)), }).Inc() } @@ -975,8 +978,9 @@ func (m *Metrics) RecordAdsCertSignTime(adsCertSignTime time.Duration) { } func (m *Metrics) RecordBidValidationCreativeSizeError(adapter openrtb_ext.BidderName, account string) { + lowerCasedAdapter := strings.ToLower(string(adapter)) m.adapterBidResponseValidationSizeError.With(prometheus.Labels{ - adapterLabel: string(adapter), successLabel: successLabel, + adapterLabel: lowerCasedAdapter, successLabel: successLabel, }).Inc() if !m.metricsDisabled.AccountAdapterDetails && account != metrics.PublisherUnknown { @@ -987,8 +991,9 @@ func (m *Metrics) RecordBidValidationCreativeSizeError(adapter openrtb_ext.Bidde } func (m *Metrics) RecordBidValidationCreativeSizeWarn(adapter openrtb_ext.BidderName, account string) { + lowerCasedAdapter := strings.ToLower(string(adapter)) m.adapterBidResponseValidationSizeWarn.With(prometheus.Labels{ - adapterLabel: string(adapter), successLabel: successLabel, + adapterLabel: lowerCasedAdapter, successLabel: successLabel, }).Inc() if !m.metricsDisabled.AccountAdapterDetails && account != metrics.PublisherUnknown { @@ -1000,7 +1005,7 @@ func (m *Metrics) RecordBidValidationCreativeSizeWarn(adapter openrtb_ext.Bidder func (m *Metrics) RecordBidValidationSecureMarkupError(adapter openrtb_ext.BidderName, account string) { m.adapterBidResponseSecureMarkupError.With(prometheus.Labels{ - adapterLabel: string(adapter), successLabel: successLabel, + adapterLabel: strings.ToLower(string(adapter)), successLabel: successLabel, }).Inc() if !m.metricsDisabled.AccountAdapterDetails && account != metrics.PublisherUnknown { @@ -1012,7 +1017,7 @@ func (m *Metrics) RecordBidValidationSecureMarkupError(adapter openrtb_ext.Bidde func (m *Metrics) RecordBidValidationSecureMarkupWarn(adapter openrtb_ext.BidderName, account string) { m.adapterBidResponseSecureMarkupWarn.With(prometheus.Labels{ - adapterLabel: string(adapter), successLabel: successLabel, + adapterLabel: strings.ToLower(string(adapter)), successLabel: successLabel, }).Inc() if !m.metricsDisabled.AccountAdapterDetails && account != metrics.PublisherUnknown { diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index e57ae5e198a..bea0087c6ea 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -226,18 +226,19 @@ func TestBidValidationCreativeSizeMetric(t *testing.T) { expectedAccountCount: 0, }, } - + adapterName := openrtb_ext.BidderName("AnyName") + lowerCasedAdapterName := "anyname" for _, test := range testCases { m := createMetricsForTesting() m.metricsDisabled.AccountAdapterDetails = test.givenAccountAdapterMetricsDisabled - m.RecordBidValidationCreativeSizeError(adapterLabel, "acct-id") - m.RecordBidValidationCreativeSizeWarn(adapterLabel, "acct-id") + m.RecordBidValidationCreativeSizeError(adapterName, "acct-id") + m.RecordBidValidationCreativeSizeWarn(adapterName, "acct-id") assertCounterVecValue(t, "", "account bid validation", m.accountBidResponseValidationSizeError, test.expectedAccountCount, prometheus.Labels{accountLabel: "acct-id", successLabel: successLabel}) - assertCounterVecValue(t, "", "adapter bid validation", m.adapterBidResponseValidationSizeError, test.expectedAdapterCount, prometheus.Labels{adapterLabel: adapterLabel, successLabel: successLabel}) + assertCounterVecValue(t, "", "adapter bid validation", m.adapterBidResponseValidationSizeError, test.expectedAdapterCount, prometheus.Labels{adapterLabel: lowerCasedAdapterName, successLabel: successLabel}) assertCounterVecValue(t, "", "account bid validation", m.accountBidResponseValidationSizeWarn, test.expectedAccountCount, prometheus.Labels{accountLabel: "acct-id", successLabel: successLabel}) - assertCounterVecValue(t, "", "adapter bid validation", m.adapterBidResponseValidationSizeWarn, test.expectedAdapterCount, prometheus.Labels{adapterLabel: adapterLabel, successLabel: successLabel}) + assertCounterVecValue(t, "", "adapter bid validation", m.adapterBidResponseValidationSizeWarn, test.expectedAdapterCount, prometheus.Labels{adapterLabel: lowerCasedAdapterName, successLabel: successLabel}) } } @@ -263,17 +264,19 @@ func TestBidValidationSecureMarkupMetric(t *testing.T) { }, } + adapterName := openrtb_ext.BidderName("AnyName") + lowerCasedAdapterName := "anyname" for _, test := range testCases { m := createMetricsForTesting() m.metricsDisabled.AccountAdapterDetails = test.givenAccountAdapterMetricsDisabled - m.RecordBidValidationSecureMarkupError(adapterLabel, "acct-id") - m.RecordBidValidationSecureMarkupWarn(adapterLabel, "acct-id") + m.RecordBidValidationSecureMarkupError(adapterName, "acct-id") + m.RecordBidValidationSecureMarkupWarn(adapterName, "acct-id") assertCounterVecValue(t, "", "Account Secure Markup Error", m.accountBidResponseSecureMarkupError, test.expectedAccountCount, prometheus.Labels{accountLabel: "acct-id", successLabel: successLabel}) - assertCounterVecValue(t, "", "Adapter Secure Markup Error", m.adapterBidResponseSecureMarkupError, test.expectedAdapterCount, prometheus.Labels{adapterLabel: adapterLabel, successLabel: successLabel}) + assertCounterVecValue(t, "", "Adapter Secure Markup Error", m.adapterBidResponseSecureMarkupError, test.expectedAdapterCount, prometheus.Labels{adapterLabel: lowerCasedAdapterName, successLabel: successLabel}) assertCounterVecValue(t, "", "Account Secure Markup Warn", m.accountBidResponseSecureMarkupWarn, test.expectedAccountCount, prometheus.Labels{accountLabel: "acct-id", successLabel: successLabel}) - assertCounterVecValue(t, "", "Adapter Secure Markup Warn", m.adapterBidResponseSecureMarkupWarn, test.expectedAdapterCount, prometheus.Labels{adapterLabel: adapterLabel, successLabel: successLabel}) + assertCounterVecValue(t, "", "Adapter Secure Markup Warn", m.adapterBidResponseSecureMarkupWarn, test.expectedAdapterCount, prometheus.Labels{adapterLabel: lowerCasedAdapterName, successLabel: successLabel}) } } @@ -768,10 +771,11 @@ func TestRecordStoredDataError(t *testing.T) { } func TestAdapterBidReceivedMetric(t *testing.T) { - adapterName := "anyName" + adapterName := openrtb_ext.BidderName("anyName") + lowerCasedAdapterName := "anyname" performTest := func(m *Metrics, hasAdm bool) { labels := metrics.AdapterLabels{ - Adapter: openrtb_ext.BidderName(adapterName), + Adapter: adapterName, } bidType := openrtb_ext.BidTypeBanner m.RecordAdapterBidReceived(labels, bidType, hasAdm) @@ -809,13 +813,13 @@ func TestAdapterBidReceivedMetric(t *testing.T) { assertCounterVecValue(t, test.description, "adapterBids[adm]", m.adapterBids, test.expectedAdmCount, prometheus.Labels{ - adapterLabel: adapterName, + adapterLabel: lowerCasedAdapterName, markupDeliveryLabel: markupDeliveryAdm, }) assertCounterVecValue(t, test.description, "adapterBids[nurl]", m.adapterBids, test.expectedNurlCount, prometheus.Labels{ - adapterLabel: adapterName, + adapterLabel: lowerCasedAdapterName, markupDeliveryLabel: markupDeliveryNurl, }) } @@ -824,6 +828,7 @@ func TestAdapterBidReceivedMetric(t *testing.T) { func TestRecordAdapterPriceMetric(t *testing.T) { m := createMetricsForTesting() adapterName := "anyName" + lowerCasedAdapterName := "anyname" cpm := float64(42) m.RecordAdapterPrice(metrics.AdapterLabels{ @@ -832,12 +837,13 @@ func TestRecordAdapterPriceMetric(t *testing.T) { expectedCount := uint64(1) expectedSum := cpm - result := getHistogramFromHistogramVec(m.adapterPrices, adapterLabel, adapterName) + result := getHistogramFromHistogramVec(m.adapterPrices, adapterLabel, lowerCasedAdapterName) assertHistogram(t, "adapterPrices", result, expectedCount, expectedSum) } func TestAdapterRequestMetrics(t *testing.T) { adapterName := "anyName" + lowerCasedAdapterName := "anyname" performTest := func(m *Metrics, cookieFlag metrics.CookieFlag, adapterBids metrics.AdapterBid) { labels := metrics.AdapterLabels{ Adapter: openrtb_ext.BidderName(adapterName), @@ -937,7 +943,7 @@ func TestAdapterRequestMetrics(t *testing.T) { processMetrics(m.adapterRequests, func(m dto.Metric) { isMetricForAdapter := false for _, label := range m.GetLabel() { - if label.GetName() == adapterLabel && label.GetValue() == adapterName { + if label.GetName() == adapterLabel && label.GetValue() == lowerCasedAdapterName { isMetricForAdapter = true } } @@ -974,6 +980,7 @@ func TestAdapterRequestMetrics(t *testing.T) { func TestAdapterRequestErrorMetrics(t *testing.T) { adapterName := "anyName" + lowerCasedAdapterName := "anyname" performTest := func(m *Metrics, adapterErrors map[metrics.AdapterError]struct{}) { labels := metrics.AdapterLabels{ Adapter: openrtb_ext.BidderName(adapterName), @@ -1030,7 +1037,7 @@ func TestAdapterRequestErrorMetrics(t *testing.T) { processMetrics(m.adapterErrors, func(m dto.Metric) { isMetricForAdapter := false for _, label := range m.GetLabel() { - if label.GetName() == adapterLabel && label.GetValue() == adapterName { + if label.GetName() == adapterLabel && label.GetValue() == lowerCasedAdapterName { isMetricForAdapter = true } } @@ -1052,6 +1059,7 @@ func TestAdapterRequestErrorMetrics(t *testing.T) { func TestAdapterTimeMetric(t *testing.T) { adapterName := "anyName" + lowerCasedAdapterName := "anyname" performTest := func(m *Metrics, timeInMs float64, adapterErrors map[metrics.AdapterError]struct{}) { m.RecordAdapterTime(metrics.AdapterLabels{ Adapter: openrtb_ext.BidderName(adapterName), @@ -1090,24 +1098,24 @@ func TestAdapterTimeMetric(t *testing.T) { test.testCase(m) - result := getHistogramFromHistogramVec(m.adapterRequestsTimer, adapterLabel, adapterName) + result := getHistogramFromHistogramVec(m.adapterRequestsTimer, adapterLabel, lowerCasedAdapterName) assertHistogram(t, test.description, result, test.expectedCount, test.expectedSum) } } func TestAdapterPanicMetric(t *testing.T) { m := createMetricsForTesting() - adapterName := "anyName" - + adapterName := openrtb_ext.BidderName("anyName") + lowerCasedAdapterName := "anyname" m.RecordAdapterPanic(metrics.AdapterLabels{ - Adapter: openrtb_ext.BidderName(adapterName), + Adapter: adapterName, }) expectedCount := float64(1) assertCounterVecValue(t, "", "adapterPanics", m.adapterPanics, expectedCount, prometheus.Labels{ - adapterLabel: adapterName, + adapterLabel: lowerCasedAdapterName, }) } @@ -1509,6 +1517,8 @@ func TestRecordBidderServerResponseTime(t *testing.T) { } func TestRecordAdapterConnections(t *testing.T) { + adapterName := openrtb_ext.BidderName("Adapter") + lowerCasedAdapterName := "adapter" type testIn struct { adapterName openrtb_ext.BidderName @@ -1531,7 +1541,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "[1] Successful, new connection created, was idle, has connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: adapterName, connWasReused: false, connWait: time.Second * 5, }, @@ -1545,7 +1555,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "[2] Successful, new connection created, not idle, has connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: adapterName, connWasReused: false, connWait: time.Second * 4, }, @@ -1559,7 +1569,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "[3] Successful, was reused, was idle, no connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: adapterName, connWasReused: true, }, out: testOut{ @@ -1572,7 +1582,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "[4] Successful, was reused, not idle, has connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: adapterName, connWasReused: true, connWait: time.Second * 5, }, @@ -1602,7 +1612,7 @@ func TestRecordAdapterConnections(t *testing.T) { "adapter_connection_reused", m.adapterReusedConnections, float64(test.out.expectedConnReusedCount), - prometheus.Labels{adapterLabel: string(test.in.adapterName)}) + prometheus.Labels{adapterLabel: lowerCasedAdapterName}) // Assert number of new created connections assertCounterVecValue(t, @@ -1610,10 +1620,10 @@ func TestRecordAdapterConnections(t *testing.T) { "adapter_connection_created", m.adapterCreatedConnections, float64(test.out.expectedConnCreatedCount), - prometheus.Labels{adapterLabel: string(test.in.adapterName)}) + prometheus.Labels{adapterLabel: lowerCasedAdapterName}) // Assert connection wait time - histogram := getHistogramFromHistogramVec(m.adapterConnectionWaitTime, adapterLabel, string(test.in.adapterName)) + histogram := getHistogramFromHistogramVec(m.adapterConnectionWaitTime, adapterLabel, lowerCasedAdapterName) assert.Equal(t, test.out.expectedConnWaitCount, histogram.GetSampleCount(), assertDesciptions[2]) assert.Equal(t, test.out.expectedConnWaitTime, histogram.GetSampleSum(), assertDesciptions[3]) } @@ -1781,8 +1791,9 @@ func assertHistogram(t *testing.T, name string, histogram dto.Histogram, expecte func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { m := createMetricsForTesting() - - m.RecordAdapterGDPRRequestBlocked(openrtb_ext.BidderAppnexus) + adapterName := openrtb_ext.BidderName("AnyName") + lowerCasedAdapterName := "anyname" + m.RecordAdapterGDPRRequestBlocked(adapterName) assertCounterVecValue(t, "Increment adapter GDPR request blocked counter", @@ -1790,7 +1801,7 @@ func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { m.adapterGDPRBlockedRequests, 1, prometheus.Labels{ - adapterLabel: string(openrtb_ext.BidderAppnexus), + adapterLabel: lowerCasedAdapterName, }) } diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go index 0ae5366b3f7..9bf2ec94a08 100644 --- a/metrics/prometheus/type_conversion.go +++ b/metrics/prometheus/type_conversion.go @@ -2,6 +2,7 @@ package prometheusmetrics import ( "strconv" + "strings" ) func enumAsString[T ~string](values []T) []string { @@ -12,6 +13,14 @@ func enumAsString[T ~string](values []T) []string { return valuesAsString } +func enumAsLowerCaseString[T ~string](values []T) []string { + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = strings.ToLower(string(v)) + } + return valuesAsString +} + func boolValuesAsString() []string { return []string{ strconv.FormatBool(true), From 6c6aa9ac75bc99deef78fa5870307cf1e04dd22c Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Mon, 16 Oct 2023 18:43:30 +0530 Subject: [PATCH 051/138] InfluxDB metrics : bidder name should be in lowercase (#3209) co-authored by @onkarvhanumante --- metrics/config/metrics_test.go | 14 +- metrics/go_metrics.go | 87 +++++---- metrics/go_metrics_test.go | 253 ++++++++++++++++++++------ metrics/metrics.go | 1 + metrics/prometheus/prometheus_test.go | 2 +- 5 files changed, 259 insertions(+), 98 deletions(-) diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index f77dcf005d8..1eae1a25545 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -2,6 +2,7 @@ package config import ( "fmt" + "strings" "testing" "time" @@ -168,13 +169,14 @@ func TestMultiMetricsEngine(t *testing.T) { VerifyMetrics(t, "Request", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusOK].Count(), 5) VerifyMetrics(t, "ImpMeter", goEngine.ImpMeter.Count(), 8) VerifyMetrics(t, "NoCookieMeter", goEngine.NoCookieMeter.Count(), 0) - VerifyMetrics(t, "AdapterMetrics.Pubmatic.GotBidsMeter", goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].GotBidsMeter.Count(), 5) - VerifyMetrics(t, "AdapterMetrics.Pubmatic.NoBidMeter", goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].NoBidMeter.Count(), 0) + + VerifyMetrics(t, "AdapterMetrics.pubmatic.GotBidsMeter", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderPubmatic))].GotBidsMeter.Count(), 5) + VerifyMetrics(t, "AdapterMetrics.pubmatic.NoBidMeter", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderPubmatic))].NoBidMeter.Count(), 0) for _, err := range metrics.AdapterErrors() { - VerifyMetrics(t, "AdapterMetrics.Pubmatic.Request.ErrorMeter."+string(err), goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].ErrorMeters[err].Count(), 0) + VerifyMetrics(t, "AdapterMetrics.pubmatic.Request.ErrorMeter."+string(err), goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderPubmatic))].ErrorMeters[err].Count(), 0) } - VerifyMetrics(t, "AdapterMetrics.AppNexus.GotBidsMeter", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].GotBidsMeter.Count(), 0) - VerifyMetrics(t, "AdapterMetrics.AppNexus.NoBidMeter", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].NoBidMeter.Count(), 5) + VerifyMetrics(t, "AdapterMetrics.appnexus.GotBidsMeter", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].GotBidsMeter.Count(), 0) + VerifyMetrics(t, "AdapterMetrics.appnexus.NoBidMeter", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].NoBidMeter.Count(), 5) VerifyMetrics(t, "RecordRequestQueueTime.Video.Rejected", goEngine.RequestsQueueTimer[metrics.ReqTypeVideo][false].Count(), 1) VerifyMetrics(t, "RecordRequestQueueTime.Video.Accepted", goEngine.RequestsQueueTimer[metrics.ReqTypeVideo][true].Count(), 0) @@ -186,7 +188,7 @@ func TestMultiMetricsEngine(t *testing.T) { VerifyMetrics(t, "StoredImpCache.Hit", goEngine.StoredImpCacheMeter[metrics.CacheHit].Count(), 5) VerifyMetrics(t, "AccountCache.Hit", goEngine.AccountCacheMeter[metrics.CacheHit].Count(), 6) - VerifyMetrics(t, "AdapterMetrics.AppNexus.GDPRRequestBlocked", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].GDPRRequestBlocked.Count(), 1) + VerifyMetrics(t, "AdapterMetrics.appNexus.GDPRRequestBlocked", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].GDPRRequestBlocked.Count(), 1) // verify that each module has its own metric recorded for module, stages := range modulesStages { diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 0d60a7009d9..13cc022c9c8 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -2,6 +2,7 @@ package metrics import ( "fmt" + "strings" "sync" "time" @@ -62,12 +63,13 @@ type Metrics struct { PrivacyLMTRequest metrics.Meter PrivacyTCFRequestVersion map[TCFVersionValue]metrics.Meter - AdapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics + AdapterMetrics map[string]*AdapterMetrics // Don't export accountMetrics because we need helper functions here to insure its properly populated dynamically accountMetrics map[string]*accountMetrics accountMetricsRWMutex sync.RWMutex - exchanges []openrtb_ext.BidderName + // adapter name exchanges + exchanges []string modules []string // Will hold boolean values to help us disable metric collection if needed MetricsDisabled config.DisabledMetrics @@ -117,7 +119,7 @@ type accountMetrics struct { bidsReceivedMeter metrics.Meter priceHistogram metrics.Histogram // store account by adapter metrics. Type is map[PBSBidder.BidderCode] - adapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics + adapterMetrics map[string]*AdapterMetrics moduleMetrics map[string]*ModuleMetrics storedResponsesMeter metrics.Meter @@ -145,7 +147,7 @@ type ModuleMetrics struct { // rather than loading metrics that never get filled. // This will also eventually let us configure metrics, such as setting a limited set of metrics // for a production instance, and then expanding again when we need more debugging. -func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disabledMetrics config.DisabledMetrics, moduleStageNames map[string][]string) *Metrics { +func NewBlankMetrics(registry metrics.Registry, exchanges []string, disabledMetrics config.DisabledMetrics, moduleStageNames map[string][]string) *Metrics { blankMeter := &metrics.NilMeter{} blankTimer := &metrics.NilTimer{} @@ -193,7 +195,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa PrivacyLMTRequest: blankMeter, PrivacyTCFRequestVersion: make(map[TCFVersionValue]metrics.Meter, len(TCFVersions())), - AdapterMetrics: make(map[openrtb_ext.BidderName]*AdapterMetrics, len(exchanges)), + AdapterMetrics: make(map[string]*AdapterMetrics, len(exchanges)), accountMetrics: make(map[string]*accountMetrics), MetricsDisabled: disabledMetrics, @@ -272,7 +274,11 @@ func getModuleNames(moduleStageNames map[string][]string) []string { // mode metrics. The code would allways try to record the metrics, but effectively noop if we are // using a blank meter/timer. func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disableAccountMetrics config.DisabledMetrics, syncerKeys []string, moduleStageNames map[string][]string) *Metrics { - newMetrics := NewBlankMetrics(registry, exchanges, disableAccountMetrics, moduleStageNames) + lowerCaseExchanges := []string{} + for _, exchange := range exchanges { + lowerCaseExchanges = append(lowerCaseExchanges, strings.ToLower(string(exchange))) + } + newMetrics := NewBlankMetrics(registry, lowerCaseExchanges, disableAccountMetrics, moduleStageNames) newMetrics.ConnectionCounter = metrics.GetOrRegisterCounter("active_connections", registry) newMetrics.TMaxTimeoutCounter = metrics.GetOrRegisterCounter("tmax_timeout", registry) newMetrics.ConnectionAcceptErrorMeter = metrics.GetOrRegisterMeter("connection_accept_errors", registry) @@ -331,7 +337,7 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d } } - for _, a := range exchanges { + for _, a := range lowerCaseExchanges { registerAdapterMetrics(registry, "adapter", string(a), newMetrics.AdapterMetrics[a]) } @@ -546,7 +552,7 @@ func (me *Metrics) getAccountMetrics(id string) *accountMetrics { am.debugRequestMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.debug_requests", id), me.MetricsRegistry) am.bidsReceivedMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.bids_received", id), me.MetricsRegistry) am.priceHistogram = metrics.GetOrRegisterHistogram(fmt.Sprintf("account.%s.prices", id), me.MetricsRegistry, metrics.NewExpDecaySample(1028, 0.015)) - am.adapterMetrics = make(map[openrtb_ext.BidderName]*AdapterMetrics, len(me.exchanges)) + am.adapterMetrics = make(map[string]*AdapterMetrics, len(me.exchanges)) am.moduleMetrics = make(map[string]*ModuleMetrics) am.storedResponsesMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.stored_responses", id), me.MetricsRegistry) if !me.MetricsDisabled.AccountAdapterDetails { @@ -670,9 +676,11 @@ func (me *Metrics) RecordStoredDataError(labels StoredDataLabels) { // RecordAdapterPanic implements a part of the MetricsEngine interface func (me *Metrics) RecordAdapterPanic(labels AdapterLabels) { - am, ok := me.AdapterMetrics[labels.Adapter] + adapterStr := string(labels.Adapter) + lowerCaseAdapterName := strings.ToLower(adapterStr) + am, ok := me.AdapterMetrics[lowerCaseAdapterName] if !ok { - glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", string(labels.Adapter)) + glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", adapterStr) return } am.PanicMeter.Mark(1) @@ -680,13 +688,15 @@ func (me *Metrics) RecordAdapterPanic(labels AdapterLabels) { // RecordAdapterRequest implements a part of the MetricsEngine interface func (me *Metrics) RecordAdapterRequest(labels AdapterLabels) { - am, ok := me.AdapterMetrics[labels.Adapter] + adapterStr := string(labels.Adapter) + lowerCaseAdapter := strings.ToLower(adapterStr) + am, ok := me.AdapterMetrics[lowerCaseAdapter] if !ok { - glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", string(labels.Adapter)) + glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", adapterStr) return } - aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[labels.Adapter] + aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[lowerCaseAdapter] switch labels.AdapterBids { case AdapterBidNone: am.NoBidMeter.Mark(1) @@ -719,8 +729,8 @@ func (me *Metrics) RecordAdapterConnections(adapterName openrtb_ext.BidderName, if me.MetricsDisabled.AdapterConnectionMetrics { return } - - am, ok := me.AdapterMetrics[adapterName] + lowerCaseAdapterName := strings.ToLower(string(adapterName)) + am, ok := me.AdapterMetrics[lowerCaseAdapterName] if !ok { glog.Errorf("Trying to log adapter connection metrics for %s: adapter not found", string(adapterName)) return @@ -749,16 +759,18 @@ func (me *Metrics) RecordBidderServerResponseTime(bidderServerResponseTime time. // RecordAdapterBidReceived implements a part of the MetricsEngine interface. // This tracks how many bids from each Bidder use `adm` vs. `nurl. func (me *Metrics) RecordAdapterBidReceived(labels AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) { - am, ok := me.AdapterMetrics[labels.Adapter] + adapterStr := string(labels.Adapter) + lowerCaseAdapterName := strings.ToLower(adapterStr) + am, ok := me.AdapterMetrics[lowerCaseAdapterName] if !ok { - glog.Errorf("Trying to run adapter bid metrics on %s: adapter metrics not found", string(labels.Adapter)) + glog.Errorf("Trying to run adapter bid metrics on %s: adapter metrics not found", adapterStr) return } // Adapter metrics am.BidsReceivedMeter.Mark(1) // Account-Adapter metrics - if aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[labels.Adapter]; ok { + if aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[lowerCaseAdapterName]; ok { aam.BidsReceivedMeter.Mark(1) } @@ -775,22 +787,26 @@ func (me *Metrics) RecordAdapterBidReceived(labels AdapterLabels, bidType openrt // RecordAdapterPrice implements a part of the MetricsEngine interface. Generates a histogram of winning bid prices func (me *Metrics) RecordAdapterPrice(labels AdapterLabels, cpm float64) { - am, ok := me.AdapterMetrics[labels.Adapter] + adapterStr := string(labels.Adapter) + lowercaseAdapter := strings.ToLower(adapterStr) + am, ok := me.AdapterMetrics[lowercaseAdapter] if !ok { - glog.Errorf("Trying to run adapter price metrics on %s: adapter metrics not found", string(labels.Adapter)) + glog.Errorf("Trying to run adapter price metrics on %s: adapter metrics not found", adapterStr) return } // Adapter metrics am.PriceHistogram.Update(int64(cpm)) // Account-Adapter metrics - if aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[labels.Adapter]; ok { + if aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[lowercaseAdapter]; ok { aam.PriceHistogram.Update(int64(cpm)) } } // RecordAdapterTime implements a part of the MetricsEngine interface. Records the adapter response time func (me *Metrics) RecordAdapterTime(labels AdapterLabels, length time.Duration) { - am, ok := me.AdapterMetrics[labels.Adapter] + adapterStr := string(labels.Adapter) + lowercaseAdapter := strings.ToLower(adapterStr) + am, ok := me.AdapterMetrics[lowercaseAdapter] if !ok { glog.Errorf("Trying to run adapter latency metrics on %s: adapter metrics not found", string(labels.Adapter)) return @@ -798,7 +814,7 @@ func (me *Metrics) RecordAdapterTime(labels AdapterLabels, length time.Duration) // Adapter metrics am.RequestTimer.Update(length) // Account-Adapter metrics - if aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[labels.Adapter]; ok { + if aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[lowercaseAdapter]; ok { aam.RequestTimer.Update(length) } } @@ -911,13 +927,14 @@ func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) { } func (me *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { + adapterStr := string(adapterName) if me.MetricsDisabled.AdapterGDPRRequestBlocked { return } - am, ok := me.AdapterMetrics[adapterName] + am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)] if !ok { - glog.Errorf("Trying to log adapter GDPR request blocked metric for %s: adapter not found", string(adapterName)) + glog.Errorf("Trying to log adapter GDPR request blocked metric for %s: adapter not found", adapterStr) return } @@ -937,9 +954,10 @@ func (me *Metrics) RecordAdsCertSignTime(adsCertSignTime time.Duration) { } func (me *Metrics) RecordBidValidationCreativeSizeError(adapter openrtb_ext.BidderName, pubID string) { - am, ok := me.AdapterMetrics[adapter] + adapterStr := string(adapter) + am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)] if !ok { - glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", string(adapter)) + glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", adapterStr) return } am.BidValidationCreativeSizeErrorMeter.Mark(1) @@ -951,9 +969,10 @@ func (me *Metrics) RecordBidValidationCreativeSizeError(adapter openrtb_ext.Bidd } func (me *Metrics) RecordBidValidationCreativeSizeWarn(adapter openrtb_ext.BidderName, pubID string) { - am, ok := me.AdapterMetrics[adapter] + adapterStr := string(adapter) + am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)] if !ok { - glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", string(adapter)) + glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", adapterStr) return } am.BidValidationCreativeSizeWarnMeter.Mark(1) @@ -965,9 +984,10 @@ func (me *Metrics) RecordBidValidationCreativeSizeWarn(adapter openrtb_ext.Bidde } func (me *Metrics) RecordBidValidationSecureMarkupError(adapter openrtb_ext.BidderName, pubID string) { - am, ok := me.AdapterMetrics[adapter] + adapterStr := string(adapter) + am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)] if !ok { - glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", string(adapter)) + glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", adapterStr) return } am.BidValidationSecureMarkupErrorMeter.Mark(1) @@ -979,9 +999,10 @@ func (me *Metrics) RecordBidValidationSecureMarkupError(adapter openrtb_ext.Bidd } func (me *Metrics) RecordBidValidationSecureMarkupWarn(adapter openrtb_ext.BidderName, pubID string) { - am, ok := me.AdapterMetrics[adapter] + adapterStr := string(adapter) + am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)] if !ok { - glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", string(adapter)) + glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", adapterStr) return } am.BidValidationSecureMarkupWarnMeter.Mark(1) diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 8acd1923a85..08ee65f52ac 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -15,15 +15,15 @@ func TestNewMetrics(t *testing.T) { registry := metrics.NewRegistry() syncerKeys := []string{"foo"} moduleStageNames := map[string][]string{"foobar": {"entry", "raw"}, "another_module": {"raw", "auction"}} - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys, moduleStageNames) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Adapter1"), openrtb_ext.BidderName("Adapter2")}, config.DisabledMetrics{}, syncerKeys, moduleStageNames) ensureContains(t, registry, "app_requests", m.AppRequestMeter) ensureContains(t, registry, "debug_requests", m.DebugRequestMeter) ensureContains(t, registry, "no_cookie_requests", m.NoCookieMeter) ensureContains(t, registry, "request_time", m.RequestTimer) ensureContains(t, registry, "amp_no_cookie_requests", m.AmpNoCookieMeter) - ensureContainsAdapterMetrics(t, registry, "adapter.appnexus", m.AdapterMetrics["appnexus"]) - ensureContainsAdapterMetrics(t, registry, "adapter.rubicon", m.AdapterMetrics["rubicon"]) + ensureContainsAdapterMetrics(t, registry, "adapter.adapter1", m.AdapterMetrics["adapter1"]) + ensureContainsAdapterMetrics(t, registry, "adapter.adapter2", m.AdapterMetrics["adapter2"]) ensureContains(t, registry, "cookie_sync_requests", m.CookieSyncMeter) ensureContains(t, registry, "cookie_sync_requests.ok", m.CookieSyncStatusMeter[CookieSyncOK]) ensureContains(t, registry, "cookie_sync_requests.bad_request", m.CookieSyncStatusMeter[CookieSyncBadRequest]) @@ -95,19 +95,21 @@ func TestNewMetrics(t *testing.T) { func TestRecordBidType(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) + adapterName := "FOO" + lowerCaseAdapterName := "foo" + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapterName)}, config.DisabledMetrics{}, nil, nil) m.RecordAdapterBidReceived(AdapterLabels{ - Adapter: openrtb_ext.BidderAppnexus, + Adapter: openrtb_ext.BidderName(adapterName), }, openrtb_ext.BidTypeBanner, true) - VerifyMetrics(t, "Appnexus Banner Adm Bids", m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[openrtb_ext.BidTypeBanner].AdmMeter.Count(), 1) - VerifyMetrics(t, "Appnexus Banner Nurl Bids", m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[openrtb_ext.BidTypeBanner].NurlMeter.Count(), 0) + VerifyMetrics(t, "foo Banner Adm Bids", m.AdapterMetrics[lowerCaseAdapterName].MarkupMetrics[openrtb_ext.BidTypeBanner].AdmMeter.Count(), 1) + VerifyMetrics(t, "foo Banner Nurl Bids", m.AdapterMetrics[lowerCaseAdapterName].MarkupMetrics[openrtb_ext.BidTypeBanner].NurlMeter.Count(), 0) m.RecordAdapterBidReceived(AdapterLabels{ - Adapter: openrtb_ext.BidderAppnexus, + Adapter: openrtb_ext.BidderName(adapterName), }, openrtb_ext.BidTypeVideo, false) - VerifyMetrics(t, "Appnexus Video Adm Bids", m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[openrtb_ext.BidTypeVideo].AdmMeter.Count(), 0) - VerifyMetrics(t, "Appnexus Video Nurl Bids", m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[openrtb_ext.BidTypeVideo].NurlMeter.Count(), 1) + VerifyMetrics(t, "foo Video Adm Bids", m.AdapterMetrics[lowerCaseAdapterName].MarkupMetrics[openrtb_ext.BidTypeVideo].AdmMeter.Count(), 0) + VerifyMetrics(t, "foo Video Nurl Bids", m.AdapterMetrics[lowerCaseAdapterName].MarkupMetrics[openrtb_ext.BidTypeVideo].NurlMeter.Count(), 1) } func ensureContains(t *testing.T, registry metrics.Registry, name string, metric interface{}) { @@ -198,17 +200,19 @@ func TestRecordBidTypeDisabledConfig(t *testing.T) { PubID: "acct-id", }, } - + adapter := "AnyName" + lowerCaseAdapter := "anyname" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.DisabledMetrics, nil, nil) + + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, test.DisabledMetrics, nil, nil) m.RecordAdapterBidReceived(AdapterLabels{ - Adapter: openrtb_ext.BidderAppnexus, + Adapter: openrtb_ext.BidderName(adapter), PubID: test.PubID, }, test.BidType, test.hasAdm) - assert.Equal(t, test.ExpectedAdmMeterCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[test.BidType].AdmMeter.Count(), "Appnexus Banner Adm Bids") - assert.Equal(t, test.ExpectedNurlMeterCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[test.BidType].NurlMeter.Count(), "Appnexus Banner Nurl Bids") + assert.Equal(t, test.ExpectedAdmMeterCount, m.AdapterMetrics[lowerCaseAdapter].MarkupMetrics[test.BidType].AdmMeter.Count(), "AnyName Banner Adm Bids") + assert.Equal(t, test.ExpectedNurlMeterCount, m.AdapterMetrics[lowerCaseAdapter].MarkupMetrics[test.BidType].NurlMeter.Count(), "AnyName Banner Nurl Bids") if test.DisabledMetrics.AccountAdapterDetails { assert.Len(t, m.accountMetrics[test.PubID].adapterMetrics, 0, "Test failed. Account metrics that contain adapter information are disabled, therefore we expect no entries in m.accountMetrics[accountId].adapterMetrics, we have %d \n", len(m.accountMetrics[test.PubID].adapterMetrics)) @@ -272,9 +276,10 @@ func TestRecordDebugRequest(t *testing.T) { expectedDebugCount: 0, }, } + adapter := "AnyName" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.givenDisabledMetrics, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, test.givenDisabledMetrics, nil, nil) m.RecordDebugRequest(test.givenDebugEnabledFlag, test.givenPubID) am := m.getAccountMetrics(test.givenPubID) @@ -311,16 +316,18 @@ func TestRecordBidValidationCreativeSize(t *testing.T) { expectedAccountCount: 0, }, } + adapter := "AnyName" + lowerCaseAdapter := "anyname" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.givenDisabledMetrics, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, test.givenDisabledMetrics, nil, nil) - m.RecordBidValidationCreativeSizeError(openrtb_ext.BidderAppnexus, test.givenPubID) - m.RecordBidValidationCreativeSizeWarn(openrtb_ext.BidderAppnexus, test.givenPubID) + m.RecordBidValidationCreativeSizeError(openrtb_ext.BidderName(adapter), test.givenPubID) + m.RecordBidValidationCreativeSizeWarn(openrtb_ext.BidderName(adapter), test.givenPubID) am := m.getAccountMetrics(test.givenPubID) - assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].BidValidationCreativeSizeErrorMeter.Count()) - assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].BidValidationCreativeSizeWarnMeter.Count()) + assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[lowerCaseAdapter].BidValidationCreativeSizeErrorMeter.Count()) + assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[lowerCaseAdapter].BidValidationCreativeSizeWarnMeter.Count()) assert.Equal(t, test.expectedAccountCount, am.bidValidationCreativeSizeMeter.Count()) assert.Equal(t, test.expectedAccountCount, am.bidValidationCreativeSizeWarnMeter.Count()) } @@ -353,16 +360,18 @@ func TestRecordBidValidationSecureMarkup(t *testing.T) { expectedAccountCount: 0, }, } + adapter := "AnyName" + lowerCaseAdapter := "anyname" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.givenDisabledMetrics, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, test.givenDisabledMetrics, nil, nil) - m.RecordBidValidationSecureMarkupError(openrtb_ext.BidderAppnexus, test.givenPubID) - m.RecordBidValidationSecureMarkupWarn(openrtb_ext.BidderAppnexus, test.givenPubID) + m.RecordBidValidationSecureMarkupError(openrtb_ext.BidderName(adapter), test.givenPubID) + m.RecordBidValidationSecureMarkupWarn(openrtb_ext.BidderName(adapter), test.givenPubID) am := m.getAccountMetrics(test.givenPubID) - assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].BidValidationSecureMarkupErrorMeter.Count()) - assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].BidValidationSecureMarkupWarnMeter.Count()) + assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[lowerCaseAdapter].BidValidationSecureMarkupErrorMeter.Count()) + assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[lowerCaseAdapter].BidValidationSecureMarkupWarnMeter.Count()) assert.Equal(t, test.expectedAccountCount, am.bidValidationSecureMarkupMeter.Count()) assert.Equal(t, test.expectedAccountCount, am.bidValidationSecureMarkupWarnMeter.Count()) } @@ -385,9 +394,10 @@ func TestRecordDNSTime(t *testing.T) { outExpDuration: time.Duration(0), }, } + adapter := "AnyName" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordDNSTime(test.inDnsLookupDuration) @@ -412,9 +422,10 @@ func TestRecordTLSHandshakeTime(t *testing.T) { expectedDuration: time.Duration(0), }, } + adapter := "AnyName" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordTLSHandshakeTime(test.tLSHandshakeDuration) @@ -442,9 +453,10 @@ func TestRecordBidderServerResponseTime(t *testing.T) { expectedSum: 1000, }, } + adapter := "AnyName" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordBidderServerResponseTime(test.time) @@ -467,7 +479,8 @@ func TestRecordAdapterConnections(t *testing.T) { expectedConnCreatedCount int64 expectedConnWaitTime time.Duration } - + adapter := "AnyName" + lowerCaseAdapterName := "anyname" testCases := []struct { description string in testIn @@ -476,7 +489,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "Successful, new connection created, has connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), connWasReused: false, connWait: time.Second * 5, connMetricsDisabled: false, @@ -490,7 +503,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "Successful, new connection created, has connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), connWasReused: false, connWait: time.Second * 4, connMetricsDisabled: false, @@ -503,7 +516,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "Successful, was reused, no connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), connWasReused: true, connMetricsDisabled: false, }, @@ -515,7 +528,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "Successful, was reused, has connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), connWasReused: true, connWait: time.Second * 5, connMetricsDisabled: false, @@ -538,7 +551,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "Adapter connection metrics are disabled, nothing gets updated", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), connWasReused: false, connWait: time.Second * 5, connMetricsDisabled: true, @@ -549,19 +562,18 @@ func TestRecordAdapterConnections(t *testing.T) { for i, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterConnectionMetrics: test.in.connMetricsDisabled}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterConnectionMetrics: test.in.connMetricsDisabled}, nil, nil) m.RecordAdapterConnections(test.in.adapterName, test.in.connWasReused, test.in.connWait) - - assert.Equal(t, test.out.expectedConnReusedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].ConnReused.Count(), "Test [%d] incorrect number of reused connections to adapter", i) - assert.Equal(t, test.out.expectedConnCreatedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].ConnCreated.Count(), "Test [%d] incorrect number of new connections to adapter created", i) - assert.Equal(t, test.out.expectedConnWaitTime.Nanoseconds(), m.AdapterMetrics[openrtb_ext.BidderAppnexus].ConnWaitTime.Sum(), "Test [%d] incorrect wait time in connection to adapter", i) + assert.Equal(t, test.out.expectedConnReusedCount, m.AdapterMetrics[lowerCaseAdapterName].ConnReused.Count(), "Test [%d] incorrect number of reused connections to adapter", i) + assert.Equal(t, test.out.expectedConnCreatedCount, m.AdapterMetrics[lowerCaseAdapterName].ConnCreated.Count(), "Test [%d] incorrect number of new connections to adapter created", i) + assert.Equal(t, test.out.expectedConnWaitTime.Nanoseconds(), m.AdapterMetrics[lowerCaseAdapterName].ConnWaitTime.Sum(), "Test [%d] incorrect wait time in connection to adapter", i) } } func TestNewMetricsWithDisabledConfig(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true, AccountModulesMetrics: true}, nil, map[string][]string{"foobar": {"entry", "raw"}}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo"), openrtb_ext.BidderName("bar")}, config.DisabledMetrics{AccountAdapterDetails: true, AccountModulesMetrics: true}, nil, map[string][]string{"foobar": {"entry", "raw"}}) assert.True(t, m.MetricsDisabled.AccountAdapterDetails, "Accound adapter metrics should be disabled") assert.True(t, m.MetricsDisabled.AccountModulesMetrics, "Accound modules metrics should be disabled") @@ -569,7 +581,7 @@ func TestNewMetricsWithDisabledConfig(t *testing.T) { func TestRecordPrebidCacheRequestTimeWithSuccess(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo")}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordPrebidCacheRequestTime(true, 42) @@ -579,7 +591,7 @@ func TestRecordPrebidCacheRequestTimeWithSuccess(t *testing.T) { func TestRecordPrebidCacheRequestTimeWithNotSuccess(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo")}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordPrebidCacheRequestTime(false, 42) @@ -647,7 +659,7 @@ func TestRecordStoredDataFetchTime(t *testing.T) { for _, tt := range tests { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo"), openrtb_ext.BidderName("Bar")}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordStoredDataFetchTime(StoredDataLabels{ DataType: tt.dataType, DataFetchType: tt.fetchType, @@ -721,7 +733,7 @@ func TestRecordStoredDataError(t *testing.T) { for _, tt := range tests { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo"), openrtb_ext.BidderName("Bar")}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordStoredDataError(StoredDataLabels{ DataType: tt.dataType, Error: tt.errorType, @@ -734,7 +746,7 @@ func TestRecordStoredDataError(t *testing.T) { func TestRecordRequestPrivacy(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo"), openrtb_ext.BidderName("Bar")}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) // CCPA m.RecordRequestPrivacy(PrivacyLabels{ @@ -780,6 +792,8 @@ func TestRecordRequestPrivacy(t *testing.T) { func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { var fakeBidder openrtb_ext.BidderName = "fooAdvertising" + adapter := "AnyName" + lowerCaseAdapterName := "anyname" tests := []struct { description string @@ -790,7 +804,7 @@ func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { { description: "", metricsDisabled: false, - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), expectedCount: 1, }, { @@ -802,24 +816,23 @@ func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { { description: "", metricsDisabled: true, - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), expectedCount: 0, }, } - for _, tt := range tests { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}, nil, nil) m.RecordAdapterGDPRRequestBlocked(tt.adapterName) - assert.Equal(t, tt.expectedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].GDPRRequestBlocked.Count(), tt.description) + assert.Equal(t, tt.expectedCount, m.AdapterMetrics[lowerCaseAdapterName].GDPRRequestBlocked.Count(), tt.description) } } func TestRecordCookieSync(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo"), openrtb_ext.BidderName("Bar")}, config.DisabledMetrics{}, nil, nil) // Known m.RecordCookieSync(CookieSyncBadRequest) @@ -837,7 +850,7 @@ func TestRecordCookieSync(t *testing.T) { func TestRecordSyncerRequest(t *testing.T) { registry := metrics.NewRegistry() syncerKeys := []string{"foo"} - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Adapter1"), openrtb_ext.BidderName("Adapter2")}, config.DisabledMetrics{}, syncerKeys, nil) // Known m.RecordSyncerRequest("foo", SyncerCookieSyncOK) @@ -856,7 +869,7 @@ func TestRecordSyncerRequest(t *testing.T) { func TestRecordSetUid(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo"), openrtb_ext.BidderName("Bar")}, config.DisabledMetrics{}, nil, nil) // Known m.RecordSetUid(SetUidOptOut) @@ -875,7 +888,7 @@ func TestRecordSetUid(t *testing.T) { func TestRecordSyncerSet(t *testing.T) { registry := metrics.NewRegistry() syncerKeys := []string{"foo"} - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Adapter1"), openrtb_ext.BidderName("Adapter2")}, config.DisabledMetrics{}, syncerKeys, nil) // Known m.RecordSyncerSet("foo", SyncerSetUidCleared) @@ -929,7 +942,7 @@ func TestStoredResponses(t *testing.T) { } for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountStoredResponses: test.accountStoredResponsesMetricsDisabled}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("AnyName")}, config.DisabledMetrics{AccountStoredResponses: test.accountStoredResponsesMetricsDisabled}, nil, nil) m.RecordStoredResponse(test.givenPubID) am := m.getAccountMetrics(test.givenPubID) @@ -963,7 +976,7 @@ func TestRecordAdsCertSignTime(t *testing.T) { } for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("AnyName")}, config.DisabledMetrics{}, nil, nil) m.RecordAdsCertSignTime(test.inAdsCertSignDuration) @@ -994,7 +1007,7 @@ func TestRecordAdsCertReqMetric(t *testing.T) { for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("AnyName")}, config.DisabledMetrics{}, nil, nil) m.RecordAdsCertReq(test.requestSuccess) @@ -1131,3 +1144,127 @@ func VerifyMetrics(t *testing.T, name string, expected int64, actual int64) { t.Errorf("Error in metric %s: expected %d, got %d.", name, expected, actual) } } + +func TestRecordAdapterPanic(t *testing.T) { + registry := metrics.NewRegistry() + adapter := "AnyName" + lowerCaseAdapterName := "anyname" + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AccountAdapterDetails: true, AccountModulesMetrics: true}, nil, map[string][]string{"foobar": {"entry", "raw"}}) + m.RecordAdapterPanic(AdapterLabels{Adapter: openrtb_ext.BidderName(adapter)}) + assert.Equal(t, m.AdapterMetrics[lowerCaseAdapterName].PanicMeter.Count(), int64(1)) +} + +func TestRecordAdapterPrice(t *testing.T) { + registry := metrics.NewRegistry() + syncerKeys := []string{"foo"} + adapter := "AnyName" + lowerCaseAdapterName := "anyname" + pubID := "pub1" + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter), openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, syncerKeys, nil) + m.RecordAdapterPrice(AdapterLabels{Adapter: openrtb_ext.BidderName(adapter), PubID: pubID}, 1000) + assert.Equal(t, m.AdapterMetrics[lowerCaseAdapterName].PriceHistogram.Max(), int64(1000)) + assert.Equal(t, m.getAccountMetrics(pubID).adapterMetrics[lowerCaseAdapterName].PriceHistogram.Max(), int64(1000)) +} + +func TestRecordAdapterTime(t *testing.T) { + registry := metrics.NewRegistry() + syncerKeys := []string{"foo"} + adapter := "AnyName" + lowerCaseAdapterName := "anyname" + pubID := "pub1" + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter), openrtb_ext.BidderAppnexus, openrtb_ext.BidderName("Adapter2")}, config.DisabledMetrics{}, syncerKeys, nil) + m.RecordAdapterTime(AdapterLabels{Adapter: openrtb_ext.BidderName(adapter), PubID: pubID}, 1000) + assert.Equal(t, m.AdapterMetrics[lowerCaseAdapterName].RequestTimer.Max(), int64(1000)) + assert.Equal(t, m.getAccountMetrics(pubID).adapterMetrics[lowerCaseAdapterName].RequestTimer.Max(), int64(1000)) +} + +func TestRecordAdapterRequest(t *testing.T) { + syncerKeys := []string{"foo"} + moduleStageNames := map[string][]string{"foobar": {"entry", "raw"}, "another_module": {"raw", "auction"}} + adapter := "AnyName" + lowerCaseAdapter := "anyname" + type errorCount struct { + badInput, badServer, timeout, failedToRequestBid, validation, tmaxTimeout, unknown int64 + } + type adapterBidsCount struct { + NoBid, GotBid int64 + } + tests := []struct { + description string + labels AdapterLabels + expectedNoCookieCount int64 + expectedAdapterBidsCount adapterBidsCount + expectedErrorCount errorCount + }{ + { + description: "no-bid", + labels: AdapterLabels{ + Adapter: openrtb_ext.BidderName(adapter), + AdapterBids: AdapterBidNone, + PubID: "acc-1", + }, + expectedAdapterBidsCount: adapterBidsCount{NoBid: 1}, + }, + { + description: "got-bid", + labels: AdapterLabels{ + Adapter: openrtb_ext.BidderName(adapter), + AdapterBids: AdapterBidPresent, + PubID: "acc-2", + }, + expectedAdapterBidsCount: adapterBidsCount{GotBid: 1}, + }, + { + description: "adapter-errors", + labels: AdapterLabels{ + Adapter: openrtb_ext.BidderName(adapter), + PubID: "acc-1", + AdapterErrors: map[AdapterError]struct{}{ + AdapterErrorBadInput: {}, + AdapterErrorBadServerResponse: {}, + AdapterErrorFailedToRequestBids: {}, + AdapterErrorTimeout: {}, + AdapterErrorValidation: {}, + AdapterErrorTmaxTimeout: {}, + AdapterErrorUnknown: {}, + }, + }, + expectedErrorCount: errorCount{ + badInput: 1, + badServer: 1, + timeout: 1, + failedToRequestBid: 1, + validation: 1, + tmaxTimeout: 1, + unknown: 1, + }, + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{}, syncerKeys, moduleStageNames) + m.RecordAdapterRequest(test.labels) + adapterMetric := m.AdapterMetrics[lowerCaseAdapter] + if assert.NotNil(t, adapterMetric) { + assert.Equal(t, test.expectedAdapterBidsCount, adapterBidsCount{ + NoBid: adapterMetric.NoBidMeter.Count(), + GotBid: adapterMetric.GotBidsMeter.Count(), + }) + } + assert.Equal(t, test.expectedNoCookieCount, adapterMetric.NoCookieMeter.Count()) + adapterErrMetric := adapterMetric.ErrorMeters + if assert.NotNil(t, adapterErrMetric) { + assert.Equal(t, test.expectedErrorCount, errorCount{ + badInput: adapterErrMetric[AdapterErrorBadInput].Count(), + badServer: adapterErrMetric[AdapterErrorBadServerResponse].Count(), + timeout: adapterErrMetric[AdapterErrorTimeout].Count(), + failedToRequestBid: adapterErrMetric[AdapterErrorFailedToRequestBids].Count(), + validation: adapterErrMetric[AdapterErrorValidation].Count(), + tmaxTimeout: adapterErrMetric[AdapterErrorTmaxTimeout].Count(), + unknown: adapterErrMetric[AdapterErrorUnknown].Count(), + }) + } + }) + } +} diff --git a/metrics/metrics.go b/metrics/metrics.go index 99e64af0835..0c3025bd296 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -282,6 +282,7 @@ func AdapterErrors() []AdapterError { AdapterErrorTimeout, AdapterErrorFailedToRequestBids, AdapterErrorValidation, + AdapterErrorTmaxTimeout, AdapterErrorUnknown, } } diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index bea0087c6ea..a2b0e84d2ea 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -64,7 +64,7 @@ func TestMetricCountGatekeeping(t *testing.T) { // Verify Per-Adapter Cardinality // - This assertion provides a warning for newly added adapter metrics. Threre are 40+ adapters which makes the // cost of new per-adapter metrics rather expensive. Thought should be given when adding new per-adapter metrics. - assert.True(t, perAdapterCardinalityCount <= 29, "Per-Adapter Cardinality count equals %d \n", perAdapterCardinalityCount) + assert.True(t, perAdapterCardinalityCount <= 30, "Per-Adapter Cardinality count equals %d \n", perAdapterCardinalityCount) } func TestConnectionMetrics(t *testing.T) { From eea6ffe5d71c9c629948e3bde0ebd4ef377c889c Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Tue, 17 Oct 2023 11:45:09 +0530 Subject: [PATCH 052/138] migrate adform alias to use new pattern (#3180) --- exchange/adapter_builders.go | 1 - openrtb_ext/bidders.go | 2 -- static/bidder-info/adform.yaml | 20 +------------------ static/bidder-params/adform.json | 33 -------------------------------- 4 files changed, 1 insertion(+), 55 deletions(-) delete mode 100644 static/bidder-params/adform.json diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 25630fed414..61af122ad1a 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -197,7 +197,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAceex: aceex.Builder, openrtb_ext.BidderAcuityAds: acuityads.Builder, openrtb_ext.BidderAdf: adf.Builder, - openrtb_ext.BidderAdform: adf.Builder, openrtb_ext.BidderAdgeneration: adgeneration.Builder, openrtb_ext.BidderAdhese: adhese.Builder, openrtb_ext.BidderAdkernel: adkernel.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 744c7fad3fe..8c4a9ea0dc4 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -23,7 +23,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderAceex, BidderAcuityAds, BidderAdf, - BidderAdform, BidderAdgeneration, BidderAdhese, BidderAdkernel, @@ -315,7 +314,6 @@ const ( BidderAceex BidderName = "aceex" BidderAcuityAds BidderName = "acuityads" BidderAdf BidderName = "adf" - BidderAdform BidderName = "adform" BidderAdgeneration BidderName = "adgeneration" BidderAdhese BidderName = "adhese" BidderAdkernel BidderName = "adkernel" diff --git a/static/bidder-info/adform.yaml b/static/bidder-info/adform.yaml index 2310f346c25..4aa6e70f1d0 100644 --- a/static/bidder-info/adform.yaml +++ b/static/bidder-info/adform.yaml @@ -1,19 +1 @@ -endpoint: "https://adx.adform.net/adx/openrtb" -maintainer: - email: "scope.sspp@adform.com" -gvlVendorID: 50 -capabilities: - app: - mediaTypes: - - banner - - native - - video - site: - mediaTypes: - - banner - - native - - video -userSync: - redirect: - url: "https://cm.adform.net/cookie?redirect_url={{.RedirectURL}}" - userMacro: "$UID" +aliasOf: adf diff --git a/static/bidder-params/adform.json b/static/bidder-params/adform.json deleted file mode 100644 index e112f122e49..00000000000 --- a/static/bidder-params/adform.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Adform Adapter Params", - "description": "A schema which validates params accepted by the adf adapter", - "type": "object", - "properties": { - "mid": { - "type": ["integer", "string"], - "pattern": "^\\d+$", - "description": "An ID which identifies the placement selling the impression" - }, - "inv": { - "type": ["integer"], - "description": "An ID which identifies the Adform inventory source id" - }, - "mname": { - "type": ["string"], - "description": "A Name which identifies the placement selling the impression" - }, - "priceType": { - "type": ["string"], - "description": "gross or net. Default is net.", - "pattern": "gross|net" - } - }, - "anyOf":[ - { - "required": ["mid"] - }, { - "required": ["inv", "mname"] - } - ] -} From 293cadc2f365d39ca452ccddea938b69f7482feb Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Tue, 17 Oct 2023 11:52:09 +0530 Subject: [PATCH 053/138] migrate copper alias to use new pattern (#3182) --- exchange/adapter_builders.go | 1 - openrtb_ext/bidders.go | 2 -- static/bidder-info/copper6.yaml | 10 +--------- static/bidder-params/copper6.json | 26 -------------------------- 4 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 static/bidder-params/copper6.json diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 61af122ad1a..f758b3ee33b 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -257,7 +257,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderConnectAd: connectad.Builder, openrtb_ext.BidderConsumable: consumable.Builder, openrtb_ext.BidderConversant: conversant.Builder, - openrtb_ext.BidderCopper6: adtelligent.Builder, openrtb_ext.BidderCpmstar: cpmstar.Builder, openrtb_ext.BidderCriteo: criteo.Builder, openrtb_ext.BidderCWire: cwire.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 8c4a9ea0dc4..8b0262bca19 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -83,7 +83,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderConnectAd, BidderConsumable, BidderConversant, - BidderCopper6, BidderCpmstar, BidderCriteo, BidderCWire, @@ -374,7 +373,6 @@ const ( BidderConnectAd BidderName = "connectad" BidderConsumable BidderName = "consumable" BidderConversant BidderName = "conversant" - BidderCopper6 BidderName = "copper6" BidderCpmstar BidderName = "cpmstar" BidderCriteo BidderName = "criteo" BidderCWire BidderName = "cwire" diff --git a/static/bidder-info/copper6.yaml b/static/bidder-info/copper6.yaml index 213cbe3624d..b4e259ef08a 100644 --- a/static/bidder-info/copper6.yaml +++ b/static/bidder-info/copper6.yaml @@ -1,15 +1,7 @@ +aliasOf: adtelligent endpoint: "http://ghb.app.copper6.com/pbs/ortb" maintainer: email: "info@copper6.com" -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video userSync: # Copper6 ssp supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-params/copper6.json b/static/bidder-params/copper6.json deleted file mode 100644 index fa4050f6c84..00000000000 --- a/static/bidder-params/copper6.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Copper6 Adapter Params", - "description": "A schema which validates params accepted by the Copper6 ssp adapter", - - "type": "object", - "properties": { - "placementId": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - }, - "siteId": { - "type": "integer", - "description": "An ID which identifies the site selling the impression" - }, - "aid": { - "type": "integer", - "description": "An ID which identifies the channel" - }, - "bidFloor": { - "type": "number", - "description": "BidFloor, US Dollars" - } - }, - "required": ["aid"] -} From 1384564fb934b487eba9f4e491268d7cdacbc4d5 Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Tue, 17 Oct 2023 12:00:53 +0530 Subject: [PATCH 054/138] update cookie sync endpoint to be case insensitive (#3103) --- endpoints/cookie_sync_test.go | 2 +- usersync/bidderfilter.go | 6 +- usersync/bidderfilter_test.go | 7 + usersync/chooser.go | 32 +++- usersync/chooser_test.go | 344 ++++++++++++++++++++++++---------- 5 files changed, 283 insertions(+), 108 deletions(-) diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 45f7a75da0c..af7819b94b9 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -87,7 +87,7 @@ func TestNewCookieSyncEndpoint(t *testing.T) { assert.IsType(t, &cookieSyncEndpoint{}, endpoint) assert.Equal(t, expected.config, result.config) - assert.Equal(t, expected.chooser, result.chooser) + assert.ObjectsAreEqualValues(expected.chooser, result.chooser) assert.Equal(t, expected.metrics, result.metrics) assert.Equal(t, expected.pbsAnalytics, result.pbsAnalytics) assert.Equal(t, expected.accountsFetcher, result.accountsFetcher) diff --git a/usersync/bidderfilter.go b/usersync/bidderfilter.go index 2d7d16ffe2b..2e4df476f26 100644 --- a/usersync/bidderfilter.go +++ b/usersync/bidderfilter.go @@ -1,5 +1,9 @@ package usersync +import ( + "strings" +) + // BidderFilter determines if a bidder has permission to perform a user sync activity. type BidderFilter interface { // Allowed returns true if the filter determines the bidder has permission and false if either @@ -40,7 +44,7 @@ func (f SpecificBidderFilter) Allowed(bidder string) bool { func NewSpecificBidderFilter(bidders []string, mode BidderFilterMode) BidderFilter { biddersLookup := make(map[string]struct{}, len(bidders)) for _, bidder := range bidders { - biddersLookup[bidder] = struct{}{} + biddersLookup[strings.ToLower(bidder)] = struct{}{} } return SpecificBidderFilter{biddersLookup: biddersLookup, mode: mode} diff --git a/usersync/bidderfilter_test.go b/usersync/bidderfilter_test.go index ddc757339a2..009c051bacc 100644 --- a/usersync/bidderfilter_test.go +++ b/usersync/bidderfilter_test.go @@ -1,6 +1,7 @@ package usersync import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -69,6 +70,12 @@ func TestSpecificBidderFilter(t *testing.T) { mode: BidderFilterMode(-1), expected: false, }, + { + description: "Case Insensitive Include - One", + bidders: []string{strings.ToUpper(bidder)}, + mode: BidderFilterModeInclude, + expected: true, + }, } for _, test := range testCases { diff --git a/usersync/chooser.go b/usersync/chooser.go index 3f478049066..578c63717de 100644 --- a/usersync/chooser.go +++ b/usersync/chooser.go @@ -1,5 +1,10 @@ package usersync +import ( + "github.com/prebid/prebid-server/openrtb_ext" + "strings" +) + // Chooser determines which syncers are eligible for a given request. type Chooser interface { // Choose considers bidders to sync, filters the bidders, and returns the result of the @@ -15,9 +20,10 @@ func NewChooser(bidderSyncerLookup map[string]Syncer) Chooser { } return standardChooser{ - bidderSyncerLookup: bidderSyncerLookup, - biddersAvailable: bidders, - bidderChooser: standardBidderChooser{shuffler: randomShuffler{}}, + bidderSyncerLookup: bidderSyncerLookup, + biddersAvailable: bidders, + bidderChooser: standardBidderChooser{shuffler: randomShuffler{}}, + normalizeValidBidderName: openrtb_ext.NormalizeBidderName, } } @@ -100,9 +106,10 @@ type Privacy interface { // standardChooser implements the user syncer algorithm per official Prebid specification. type standardChooser struct { - bidderSyncerLookup map[string]Syncer - biddersAvailable []string - bidderChooser bidderChooser + bidderSyncerLookup map[string]Syncer + biddersAvailable []string + bidderChooser bidderChooser + normalizeValidBidderName func(name string) (openrtb_ext.BidderName, bool) } // Choose randomly selects user syncers which are permitted by the user's privacy settings and @@ -136,7 +143,12 @@ func (c standardChooser) Choose(request Request, cookie *Cookie) Result { } func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{}, syncTypeFilter SyncTypeFilter, privacy Privacy, cookie *Cookie) (Syncer, BidderEvaluation) { - syncer, exists := c.bidderSyncerLookup[bidder] + bidderNormalized, exists := c.normalizeValidBidderName(bidder) + if !exists { + return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} + } + + syncer, exists := c.bidderSyncerLookup[bidderNormalized.String()] if !exists { return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} } @@ -147,7 +159,7 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} } syncersSeen[syncer.Key()] = struct{}{} - if !syncer.SupportsType(syncTypeFilter.ForBidder(bidder)) { + if !syncer.SupportsType(syncTypeFilter.ForBidder(strings.ToLower(bidder))) { return nil, BidderEvaluation{Status: StatusTypeNotSupported, Bidder: bidder, SyncerKey: syncer.Key()} } @@ -160,11 +172,11 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} return nil, BidderEvaluation{Status: StatusBlockedByPrivacy, Bidder: bidder, SyncerKey: syncer.Key()} } - if !privacy.GDPRAllowsBidderSync(bidder) { + if !privacy.GDPRAllowsBidderSync(bidderNormalized.String()) { return nil, BidderEvaluation{Status: StatusBlockedByGDPR, Bidder: bidder, SyncerKey: syncer.Key()} } - if !privacy.CCPAAllowsBidderSync(bidder) { + if !privacy.CCPAAllowsBidderSync(bidderNormalized.String()) { return nil, BidderEvaluation{Status: StatusBlockedByCCPA, Bidder: bidder, SyncerKey: syncer.Key()} } diff --git a/usersync/chooser_test.go b/usersync/chooser_test.go index 4f38002c988..6ab5a6b53e3 100644 --- a/usersync/chooser_test.go +++ b/usersync/chooser_test.go @@ -1,12 +1,13 @@ package usersync import ( + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "testing" "time" "github.com/prebid/prebid-server/macros" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) func TestNewChooser(t *testing.T) { @@ -47,7 +48,10 @@ func TestChooserChoose(t *testing.T) { fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: true} fakeSyncerC := fakeSyncer{key: "keyC", supportsIFrame: false} - bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "c": fakeSyncerC} + bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "c": fakeSyncerC, "appnexus": fakeSyncerA} + normalizedBidderNamesLookup := func(name string) (openrtb_ext.BidderName, bool) { + return openrtb_ext.BidderName(name), true + } syncerChoiceA := SyncerChoice{Bidder: "a", Syncer: fakeSyncerA} syncerChoiceB := SyncerChoice{Bidder: "b", Syncer: fakeSyncerB} syncTypeFilter := SyncTypeFilter{ @@ -61,16 +65,18 @@ func TestChooserChoose(t *testing.T) { givenRequest Request givenChosenBidders []string givenCookie Cookie + bidderNamesLookup func(name string) (openrtb_ext.BidderName, bool) expected Result }{ { description: "Cookie Opt Out", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{"a"}, givenCookie: Cookie{optOut: true}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusBlockedByUserOptOut, BiddersEvaluated: nil, @@ -80,11 +86,12 @@ func TestChooserChoose(t *testing.T) { { description: "GDPR Host Cookie Not Allowed", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: false, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: false, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{"a"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusBlockedByGDPR, BiddersEvaluated: nil, @@ -94,11 +101,12 @@ func TestChooserChoose(t *testing.T) { { description: "No Bidders", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{}, @@ -108,11 +116,12 @@ func TestChooserChoose(t *testing.T) { { description: "One Bidder - Sync", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{"a"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, @@ -122,11 +131,12 @@ func TestChooserChoose(t *testing.T) { { description: "One Bidder - No Sync", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{"c"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}}, @@ -136,11 +146,12 @@ func TestChooserChoose(t *testing.T) { { description: "Many Bidders - All Sync - Limit Disabled With 0", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{"a", "b"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "b", SyncerKey: "keyB", Status: StatusOK}}, @@ -150,11 +161,12 @@ func TestChooserChoose(t *testing.T) { { description: "Many Bidders - All Sync - Limit Disabled With Negative Value", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: -1, }, givenChosenBidders: []string{"a", "b"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "b", SyncerKey: "keyB", Status: StatusOK}}, @@ -164,11 +176,12 @@ func TestChooserChoose(t *testing.T) { { description: "Many Bidders - Limited Sync", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 1, }, givenChosenBidders: []string{"a", "b"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, @@ -178,11 +191,12 @@ func TestChooserChoose(t *testing.T) { { description: "Many Bidders - Limited Sync - Disqualified Syncers Don't Count Towards Limit", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 1, }, givenChosenBidders: []string{"c", "a", "b"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}, {Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, @@ -192,17 +206,65 @@ func TestChooserChoose(t *testing.T) { { description: "Many Bidders - Some Sync, Some Don't", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{"a", "c"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}}, SyncersChosen: []SyncerChoice{syncerChoiceA}, }, }, + { + description: "Unknown Bidder", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{}, + bidderNamesLookup: func(name string) (openrtb_ext.BidderName, bool) { + return openrtb_ext.BidderName(name), false + }, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", Status: StatusUnknownBidder}}, + SyncersChosen: []SyncerChoice{}, + }, + }, + { + description: "Case insensitive bidder name", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"AppNexus"}, + givenCookie: Cookie{}, + bidderNamesLookup: openrtb_ext.NormalizeBidderName, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusOK}}, + SyncersChosen: []SyncerChoice{{Bidder: "AppNexus", Syncer: fakeSyncerA}}, + }, + }, + { + description: "Duplicate bidder name", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"AppNexus", "appNexus"}, + givenCookie: Cookie{}, + bidderNamesLookup: openrtb_ext.NormalizeBidderName, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "appNexus", SyncerKey: "keyA", Status: StatusDuplicate}}, + SyncersChosen: []SyncerChoice{{Bidder: "AppNexus", Syncer: fakeSyncerA}}, + }, + }, } bidders := []string{"anyRequested"} @@ -219,9 +281,10 @@ func TestChooserChoose(t *testing.T) { Return(test.givenChosenBidders) chooser := standardChooser{ - bidderSyncerLookup: bidderSyncerLookup, - biddersAvailable: biddersAvailable, - bidderChooser: mockBidderChooser, + bidderSyncerLookup: bidderSyncerLookup, + biddersAvailable: biddersAvailable, + bidderChooser: mockBidderChooser, + normalizeValidBidderName: test.bidderNamesLookup, } result := chooser.Choose(test.givenRequest, &test.givenCookie) @@ -232,113 +295,199 @@ func TestChooserChoose(t *testing.T) { func TestChooserEvaluate(t *testing.T) { fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: false} - bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB} + bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "appnexus": fakeSyncerA, "suntContent": fakeSyncerA} syncTypeFilter := SyncTypeFilter{ IFrame: NewUniformBidderFilter(BidderFilterModeInclude), Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} - + normalizedBidderNamesLookup := func(name string) (openrtb_ext.BidderName, bool) { + return openrtb_ext.BidderName(name), true + } cookieNeedsSync := Cookie{} cookieAlreadyHasSyncForA := Cookie{uids: map[string]UIDEntry{"keyA": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} cookieAlreadyHasSyncForB := Cookie{uids: map[string]UIDEntry{"keyB": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} testCases := []struct { - description string - givenBidder string - givenSyncersSeen map[string]struct{} - givenPrivacy Privacy - givenCookie Cookie - expectedSyncer Syncer - expectedEvaluation BidderEvaluation + description string + givenBidder string + normalisedBidderName string + givenSyncersSeen map[string]struct{} + givenPrivacy fakePrivacy + givenCookie Cookie + givenSyncTypeFilter SyncTypeFilter + normalizedBidderNamesLookup func(name string) (openrtb_ext.BidderName, bool) + expectedSyncer Syncer + expectedEvaluation BidderEvaluation }{ { - description: "Valid", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, - givenCookie: cookieNeedsSync, - expectedSyncer: fakeSyncerA, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, + description: "Valid", + givenBidder: "a", + normalisedBidderName: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: fakeSyncerA, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, + }, + { + description: "Unknown Bidder", + givenBidder: "unknown", + normalisedBidderName: "", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "unknown", Status: StatusUnknownBidder}, + }, + { + description: "Duplicate Syncer", + givenBidder: "a", + normalisedBidderName: "", + givenSyncersSeen: map[string]struct{}{"keyA": {}}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusDuplicate}, + }, + { + description: "Incompatible Kind", + givenBidder: "b", + normalisedBidderName: "", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "b", SyncerKey: "keyB", Status: StatusTypeNotSupported}, + }, + { + description: "Already Synced", + givenBidder: "a", + normalisedBidderName: "", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieAlreadyHasSyncForA, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusAlreadySynced}, }, { - description: "Unknown Bidder", - givenBidder: "unknown", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, - givenCookie: cookieNeedsSync, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "unknown", Status: StatusUnknownBidder}, + description: "Different Bidder Already Synced", + givenBidder: "a", + normalisedBidderName: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieAlreadyHasSyncForB, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: fakeSyncerA, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, }, { - description: "Duplicate Syncer", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{"keyA": {}}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, - givenCookie: cookieNeedsSync, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusDuplicate}, + description: "Blocked By GDPR", + givenBidder: "a", + normalisedBidderName: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: false, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByGDPR}, }, { - description: "Incompatible Kind", - givenBidder: "b", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, - givenCookie: cookieNeedsSync, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "b", SyncerKey: "keyB", Status: StatusTypeNotSupported}, + description: "Blocked By CCPA", + givenBidder: "a", + normalisedBidderName: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: false, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByCCPA}, }, { - description: "Already Synced", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, - givenCookie: cookieAlreadyHasSyncForA, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusAlreadySynced}, + description: "Blocked By activity control", + givenBidder: "a", + normalisedBidderName: "", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: false}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, }, { - description: "Different Bidder Already Synced", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, - givenCookie: cookieAlreadyHasSyncForB, - expectedSyncer: fakeSyncerA, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, + description: "Case insensitive bidder name", + givenBidder: "AppNexus", + normalisedBidderName: "appnexus", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, + expectedSyncer: fakeSyncerA, + expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusOK}, }, { - description: "Blocked By GDPR", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: false, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, - givenCookie: cookieNeedsSync, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByGDPR}, + description: "Case insensitivity check for sync type filter", + givenBidder: "SuntContent", + normalisedBidderName: "suntContent", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: SyncTypeFilter{ + IFrame: NewSpecificBidderFilter([]string{"SuntContent"}, BidderFilterModeInclude), + Redirect: NewSpecificBidderFilter([]string{"SuntContent"}, BidderFilterModeExclude)}, + normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, + expectedSyncer: fakeSyncerA, + expectedEvaluation: BidderEvaluation{Bidder: "SuntContent", SyncerKey: "keyA", Status: StatusOK}, }, { - description: "Blocked By CCPA", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: false, activityAllowUserSync: true}, - givenCookie: cookieNeedsSync, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByCCPA}, + description: "Case Insensitivity Check For Blocked By GDPR", + givenBidder: "AppNexus", + normalisedBidderName: "appnexus", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: false, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusBlockedByGDPR}, }, { - description: "Blocked By activity control", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: false}, - givenCookie: cookieNeedsSync, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, + description: "Case Insensitivity Check For Blocked By CCPA", + givenBidder: "AppNexus", + normalisedBidderName: "appnexus", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: false, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusBlockedByCCPA}, }, } for _, test := range testCases { - chooser, _ := NewChooser(bidderSyncerLookup).(standardChooser) - sync, evaluation := chooser.evaluate(test.givenBidder, test.givenSyncersSeen, syncTypeFilter, test.givenPrivacy, &test.givenCookie) + t.Run(test.description, func(t *testing.T) { + chooser, _ := NewChooser(bidderSyncerLookup).(standardChooser) + chooser.normalizeValidBidderName = test.normalizedBidderNamesLookup + sync, evaluation := chooser.evaluate(test.givenBidder, test.givenSyncersSeen, test.givenSyncTypeFilter, &test.givenPrivacy, &test.givenCookie) - assert.Equal(t, test.expectedSyncer, sync, test.description+":syncer") - assert.Equal(t, test.expectedEvaluation, evaluation, test.description+":evaluation") + assert.Equal(t, test.normalisedBidderName, test.givenPrivacy.inputBidderName) + assert.Equal(t, test.expectedSyncer, sync, test.description+":syncer") + assert.Equal(t, test.expectedEvaluation, evaluation, test.description+":evaluation") + }) } } @@ -386,20 +535,23 @@ type fakePrivacy struct { gdprAllowsBidderSync bool ccpaAllowsBidderSync bool activityAllowUserSync bool + inputBidderName string } -func (p fakePrivacy) GDPRAllowsHostCookie() bool { +func (p *fakePrivacy) GDPRAllowsHostCookie() bool { return p.gdprAllowsHostCookie } -func (p fakePrivacy) GDPRAllowsBidderSync(bidder string) bool { +func (p *fakePrivacy) GDPRAllowsBidderSync(bidder string) bool { + p.inputBidderName = bidder return p.gdprAllowsBidderSync } -func (p fakePrivacy) CCPAAllowsBidderSync(bidder string) bool { +func (p *fakePrivacy) CCPAAllowsBidderSync(bidder string) bool { + p.inputBidderName = bidder return p.ccpaAllowsBidderSync } -func (p fakePrivacy) ActivityAllowsUserSync(bidder string) bool { +func (p *fakePrivacy) ActivityAllowsUserSync(bidder string) bool { return p.activityAllowUserSync } From b3e5d1d062507d071cd7833af2d91d2e118d4038 Mon Sep 17 00:00:00 2001 From: Sonali-More-Xandr <87759626+Sonali-More-Xandr@users.noreply.github.com> Date: Tue, 17 Oct 2023 20:42:02 +0530 Subject: [PATCH 055/138] Adapter Name Case Insensitive: user.ext.prebid.buyeruids (#3152) --- endpoints/openrtb2/amp_auction_test.go | 3 + endpoints/openrtb2/auction.go | 4 +- .../aliased-buyeruids-case-insensitive.json | 84 ++++++++++ .../buyeruids-camel-case.json | 77 ++++++++++ .../buyeruids-case-insensitive.json | 77 ++++++++++ .../buyeruid_case_insensitive.json | 144 ++++++++++++++++++ exchange/utils.go | 10 +- 7 files changed, 396 insertions(+), 3 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliased-buyeruids-case-insensitive.json create mode 100644 endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-camel-case.json create mode 100644 endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-case-insensitive.json create mode 100644 exchange/exchangetest/buyeruid_case_insensitive.json diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 3c0feb4f7ab..4315ec7926f 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -50,6 +50,9 @@ func TestGoodAmpRequests(t *testing.T) { "imp-with-stored-resp.json", "gdpr-no-consentstring.json", "gdpr.json", + "buyeruids-case-insensitive.json", + "buyeruids-camel-case.json", + "aliased-buyeruids-case-insensitive.json", }, }, { diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 3a929c43abe..fdb135497d0 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1817,7 +1817,9 @@ func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases return append(errL, errors.New(`request.user.ext.prebid requires a "buyeruids" property with at least one ID defined. If none exist, then request.user.ext.prebid should not be defined.`)) } for bidderName := range prebid.BuyerUIDs { - if _, ok := deps.bidderMap[bidderName]; !ok { + normalizedCoreBidder, _ := deps.normalizeBidderName(bidderName) + coreBidder := normalizedCoreBidder.String() + if _, ok := deps.bidderMap[coreBidder]; !ok { if _, ok := aliases[bidderName]; !ok { return append(errL, fmt.Errorf("request.user.ext.%s is neither a known bidder name nor an alias in request.ext.prebid.aliases", bidderName)) } diff --git a/endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliased-buyeruids-case-insensitive.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliased-buyeruids-case-insensitive.json new file mode 100644 index 00000000000..1d2462c98b8 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliased-buyeruids-case-insensitive.json @@ -0,0 +1,84 @@ +{ + "description": "Amp request where root.user.ext.prebid.buyeruids field makes use of alias defined in root.ext.prebid.aliases and request makes use of case sensitive bidder name", + "query": "tag_id=101", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 2 + } + ] + }, + "mockBidRequest": { + "id": "request-with-user-ext-obj", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "Appnexus": { + "placementId": 12883451 + } + } + } + } + } + ], + "user": { + "ext": { + "prebid": { + "buyeruids": { + "unknown": "123" + } + } + } + }, + "ext": { + "prebid": { + "aliases": { + "unknown": "Appnexus" + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "Appnexus", + "hb_bidder_Appnexus": "Appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_Appnex": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_Appnexus": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_Appnex": "/pbcache/endpoint", + "hb_pb": "2.00", + "hb_pb_Appnexus": "2.00" + }, + "ortb2": { + "ext": { + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-camel-case.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-camel-case.json new file mode 100644 index 00000000000..4597fe7786a --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-camel-case.json @@ -0,0 +1,77 @@ +{ + "description": "Amp request where root.user.ext.prebid.buyeruids field has camel case bidder name", + "query": "tag_id=101", + "config": { + "mockBidders": [ + { + "bidderName": "yahooAds", + "currency": "USD", + "price": 2 + } + ] + }, + "mockBidRequest": { + "id": "request-with-user-ext-obj", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "YahooAds": { + "placementId": 12883451 + } + } + } + } + } + ], + "user": { + "ext": { + "prebid": { + "buyeruids": { + "YahooAds": "123" + } + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "YahooAds", + "hb_bidder_YahooAds": "YahooAds", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_YahooA": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_YahooAds": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_YahooA": "/pbcache/endpoint", + "hb_pb": "2.00", + "hb_pb_YahooAds": "2.00" + }, + "ortb2": { + "ext": { + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-case-insensitive.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-case-insensitive.json new file mode 100644 index 00000000000..976fcb3cf7f --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-case-insensitive.json @@ -0,0 +1,77 @@ +{ + "description": "Amp request where root.user.ext.prebid.buyeruids field has case insensitive bidder name", + "query": "tag_id=101", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 2 + } + ] + }, + "mockBidRequest": { + "id": "request-with-user-ext-obj", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "Appnexus": { + "placementId": 12883451 + } + } + } + } + } + ], + "user": { + "ext": { + "prebid": { + "buyeruids": { + "Appnexus": "123" + } + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "Appnexus", + "hb_bidder_Appnexus": "Appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_Appnex": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_Appnexus": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_Appnex": "/pbcache/endpoint", + "hb_pb": "2.00", + "hb_pb_Appnexus": "2.00" + }, + "ortb2": { + "ext": { + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/exchange/exchangetest/buyeruid_case_insensitive.json b/exchange/exchangetest/buyeruid_case_insensitive.json new file mode 100644 index 00000000000..6999e8c9515 --- /dev/null +++ b/exchange/exchangetest/buyeruid_case_insensitive.json @@ -0,0 +1,144 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "userId", + "ext": { + "prebid": { + "buyeruids": { + "APPnexUS": "12345" + } + } + } + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "APPnexus": { + "placementId": 1 + }, + "appNEXUS": { + "placementId": 2 + }, + "amx": {} + } + } + } + } + ], + "ext": { + "prebid": { + "aliases": { + "APPnexus": "appnexus", + "appNEXUS": "appnexus" + } + } + } + } + }, + "outgoingRequests": { + "APPnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "userId", + "buyeruid": "12345" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ] + } + } + }, + "appNEXUS": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "userId", + "buyeruid": "12345" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 2 + } + } + } + ] + } + } + }, + "amx": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "userId" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + } + } + } + ] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "ext": {} + } + } +} diff --git a/exchange/utils.go b/exchange/utils.go index 0d9c90cda99..7d5beb64fb0 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -327,6 +327,12 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest, return nil, []error{err} } + lowerCaseExplicitBuyerUIDs := make(map[string]string) + for bidder, uid := range explicitBuyerUIDs { + lowerKey := strings.ToLower(bidder) + lowerCaseExplicitBuyerUIDs[lowerKey] = uid + } + var errs []error for bidder, imps := range impsByBidder { coreBidder := resolveBidder(bidder, aliases) @@ -361,7 +367,7 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest, } syncerKey := bidderToSyncerKey[string(coreBidder)] - if hadSync := prepareUser(&reqCopy, bidder, syncerKey, explicitBuyerUIDs, auctionRequest.UserSyncs); !hadSync && req.BidRequest.App == nil { + if hadSync := prepareUser(&reqCopy, bidder, syncerKey, lowerCaseExplicitBuyerUIDs, auctionRequest.UserSyncs); !hadSync && req.BidRequest.App == nil { bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagNo } else { bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagYes @@ -642,7 +648,7 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map func prepareUser(req *openrtb2.BidRequest, givenBidder, syncerKey string, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { cookieId, hadCookie, _ := usersyncs.GetUID(syncerKey) - if id, ok := explicitBuyerUIDs[givenBidder]; ok { + if id, ok := explicitBuyerUIDs[strings.ToLower(givenBidder)]; ok { req.User = copyWithBuyerUID(req.User, id) } else if hadCookie { req.User = copyWithBuyerUID(req.User, cookieId) From f152b0d3bbfe624c39d2512d3146484bfc7a4e1e Mon Sep 17 00:00:00 2001 From: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Tue, 17 Oct 2023 08:24:37 -0700 Subject: [PATCH 056/138] Adapter Name Case Insensitive: Bid Adjustment Factors (#3210) --- endpoints/openrtb2/auction.go | 7 ++ ...actors-case-invalid-same-bidder-names.json | 81 ++++++++++++++++ ...ors-case-insensitive-different-casing.json | 97 +++++++++++++++++++ ... => bid-adj-factors-case-insensitive.json} | 0 exchange/bidder.go | 4 +- exchange/bidder_test.go | 14 +-- exchange/utils.go | 8 +- exchange/utils_test.go | 5 + 8 files changed, 205 insertions(+), 11 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/bid-adj-factors-case-invalid-same-bidder-names.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive-different-casing.json rename endpoints/openrtb2/sample-requests/valid-whole/exemplary/{bidder-adjustment-factors-case-insensitive.json => bid-adj-factors-case-insensitive.json} (100%) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index fdb135497d0..0e1a23583a1 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -965,6 +965,7 @@ func validateAndFillSourceTID(req *openrtb_ext.RequestWrapper, generateRequestID } func (deps *endpointDeps) validateBidAdjustmentFactors(adjustmentFactors map[string]float64, aliases map[string]string) error { + uniqueBidders := make(map[string]struct{}) for bidderToAdjust, adjustmentFactor := range adjustmentFactors { if adjustmentFactor <= 0 { return fmt.Errorf("request.ext.prebid.bidadjustmentfactors.%s must be a positive number. Got %f", bidderToAdjust, adjustmentFactor) @@ -976,6 +977,12 @@ func (deps *endpointDeps) validateBidAdjustmentFactors(adjustmentFactors map[str bidderName = normalizedCoreBidder.String() } + if _, exists := uniqueBidders[bidderName]; exists { + return fmt.Errorf("cannot have multiple bidders that differ only in case style") + } else { + uniqueBidders[bidderName] = struct{}{} + } + if _, isBidder := deps.bidderMap[bidderName]; !isBidder { if _, isAlias := aliases[bidderToAdjust]; !isAlias { return fmt.Errorf("request.ext.prebid.bidadjustmentfactors.%s is not a known bidder or alias", bidderToAdjust) diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/bid-adj-factors-case-invalid-same-bidder-names.json b/endpoints/openrtb2/sample-requests/invalid-whole/bid-adj-factors-case-invalid-same-bidder-names.json new file mode 100644 index 00000000000..d484f129ba3 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/bid-adj-factors-case-invalid-same-bidder-names.json @@ -0,0 +1,81 @@ +{ + "description": "This demonstrates a request with bidadjustmentfactors that have multiple of the same bidder is invalid request", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "ext": { + "consent": "gdpr-consent-string" + } + }, + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1NYN" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "Appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "bidadjustmentfactors": { + "APPNEXUS": 1.01, + "Appnexus": 2.00 + }, + "cache": { + "bids": {} + }, + "channel": { + "name": "video", + "version": "1.0" + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: cannot have multiple bidders that differ only in case style" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive-different-casing.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive-different-casing.json new file mode 100644 index 00000000000..a231af657cd --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive-different-casing.json @@ -0,0 +1,97 @@ +{ + "description": "This demonstrates bid adjustment factors with bidder names that have different casing in imp vs. in bidadjustmentfactors", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "ext": { + "consent": "gdpr-consent-string" + } + }, + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1NYN" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "Appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "bidadjustmentfactors": { + "APPNEXUS": 1.01 + }, + "cache": { + "bids": {} + }, + "channel": { + "name": "video", + "version": "1.0" + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1.01 + } + ], + "seat": "Appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidder-adjustment-factors-case-insensitive.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive.json similarity index 100% rename from endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidder-adjustment-factors-case-insensitive.json rename to endpoints/openrtb2/sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive.json diff --git a/exchange/bidder.go b/exchange/bidder.go index dfdb7cdf85f..40135787b25 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -348,9 +348,9 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde } adjustmentFactor := 1.0 - if givenAdjustment, ok := bidRequestOptions.bidAdjustments[bidderName.String()]; ok { + if givenAdjustment, ok := bidRequestOptions.bidAdjustments[(strings.ToLower(bidderName.String()))]; ok { adjustmentFactor = givenAdjustment - } else if givenAdjustment, ok := bidRequestOptions.bidAdjustments[bidderRequest.BidderName.String()]; ok { + } else if givenAdjustment, ok := bidRequestOptions.bidAdjustments[(strings.ToLower(bidderRequest.BidderName.String()))]; ok { adjustmentFactor = givenAdjustment } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 65f6b795247..09c811ba9c6 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -2672,7 +2672,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { }, BidType: openrtb_ext.BidTypeBanner, DealPriority: 4, - Seat: "pubmatic", + Seat: "PUBMATIC", }, { Bid: &openrtb2.Bid{ @@ -2699,7 +2699,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "PUBMATIC"}, }}, Seat: "groupm", Currency: "USD", @@ -2715,9 +2715,9 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", OriginalBidCPM: 3, - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "PUBMATIC"}, }}, - Seat: string(openrtb_ext.BidderPubmatic), + Seat: "PUBMATIC", Currency: "USD", }, } @@ -2727,10 +2727,10 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, - BidderName: openrtb_ext.BidderPubmatic, + BidderName: "PUBMATIC", } bidAdjustments := map[string]float64{ - string(openrtb_ext.BidderPubmatic): 2, + string(openrtb_ext.BidderPubmatic): 2, // All lowercase value in bid adjustments to simulate it being case insensitive "groupm": 3, } @@ -2745,7 +2745,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { openrtb_ext.ExtAlternateBidderCodes{ Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ - string(openrtb_ext.BidderPubmatic): { + "PUBMATIC": { Enabled: true, AllowedBidderCodes: []string{"groupm"}, }, diff --git a/exchange/utils.go b/exchange/utils.go index 7d5beb64fb0..4fe9af12ff3 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -899,8 +899,12 @@ func parseRequestDebugValues(test int8, requestExtPrebid *openrtb_ext.ExtRequest } func getExtBidAdjustmentFactors(requestExtPrebid *openrtb_ext.ExtRequestPrebid) map[string]float64 { - if requestExtPrebid != nil { - return requestExtPrebid.BidAdjustmentFactors + if requestExtPrebid != nil && requestExtPrebid.BidAdjustmentFactors != nil { + caseInsensitiveMap := make(map[string]float64, len(requestExtPrebid.BidAdjustmentFactors)) + for bidder, bidAdjFactor := range requestExtPrebid.BidAdjustmentFactors { + caseInsensitiveMap[strings.ToLower(bidder)] = bidAdjFactor + } + return caseInsensitiveMap } return nil } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 0dbf5b38e5c..a1e71b30c23 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -1834,6 +1834,11 @@ func TestGetExtBidAdjustmentFactors(t *testing.T) { requestExtPrebid: &openrtb_ext.ExtRequestPrebid{BidAdjustmentFactors: map[string]float64{"bid-factor": 1.0}}, outBidAdjustmentFactors: map[string]float64{"bid-factor": 1.0}, }, + { + desc: "BidAdjustmentFactors contains uppercase bidders, expect case insensitve map returned", + requestExtPrebid: &openrtb_ext.ExtRequestPrebid{BidAdjustmentFactors: map[string]float64{"Bidder": 1.0, "APPNEXUS": 2.0}}, + outBidAdjustmentFactors: map[string]float64{"bidder": 1.0, "appnexus": 2.0}, + }, } for _, test := range testCases { actualBidAdjustmentFactors := getExtBidAdjustmentFactors(test.requestExtPrebid) From 17109ca1a600463f4ba18da6ab2bee43cf88c020 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 17 Oct 2023 23:42:12 -0400 Subject: [PATCH 057/138] Welcome Back: yeahmobi (#3228) --- adapters/yeahmobi/params_test.go | 47 +++++ adapters/yeahmobi/yeahmobi.go | 189 ++++++++++++++++++ adapters/yeahmobi/yeahmobi_test.go | 28 +++ .../yeahmobitest/exemplary/no-bid.json | 51 +++++ .../yeahmobitest/exemplary/simple-banner.json | 81 ++++++++ .../exemplary/simple-native-1.1.json | 82 ++++++++ .../yeahmobitest/exemplary/simple-native.json | 82 ++++++++ .../yeahmobitest/exemplary/simple-video.json | 89 +++++++++ .../supplemental/bad_imp_ext.json | 21 ++ .../supplemental/bad_imp_ext_bidder.json | 23 +++ .../supplemental/bad_response.json | 55 +++++ .../yeahmobitest/supplemental/status_400.json | 55 +++++ .../yeahmobitest/supplemental/status_500.json | 55 +++++ exchange/adapter_builders.go | 2 + exchange/adapter_util.go | 1 - openrtb_ext/bidders.go | 2 + openrtb_ext/imp_yeahmobi.go | 7 + static/bidder-info/yeahmobi.yaml | 9 + static/bidder-params/yeahmobi.json | 21 ++ 19 files changed, 899 insertions(+), 1 deletion(-) create mode 100644 adapters/yeahmobi/params_test.go create mode 100644 adapters/yeahmobi/yeahmobi.go create mode 100644 adapters/yeahmobi/yeahmobi_test.go create mode 100644 adapters/yeahmobi/yeahmobitest/exemplary/no-bid.json create mode 100644 adapters/yeahmobi/yeahmobitest/exemplary/simple-banner.json create mode 100644 adapters/yeahmobi/yeahmobitest/exemplary/simple-native-1.1.json create mode 100644 adapters/yeahmobi/yeahmobitest/exemplary/simple-native.json create mode 100644 adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json create mode 100644 adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext.json create mode 100644 adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext_bidder.json create mode 100644 adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json create mode 100644 adapters/yeahmobi/yeahmobitest/supplemental/status_400.json create mode 100644 adapters/yeahmobi/yeahmobitest/supplemental/status_500.json create mode 100644 openrtb_ext/imp_yeahmobi.go create mode 100644 static/bidder-info/yeahmobi.yaml create mode 100644 static/bidder-params/yeahmobi.json diff --git a/adapters/yeahmobi/params_test.go b/adapters/yeahmobi/params_test.go new file mode 100644 index 00000000000..997bf93a53f --- /dev/null +++ b/adapters/yeahmobi/params_test.go @@ -0,0 +1,47 @@ +package yeahmobi + +import ( + "encoding/json" + "github.com/prebid/prebid-server/openrtb_ext" + "testing" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderYeahmobi, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected yeahmobi params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the yeahmobi schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderYeahmobi, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"pubId": "11233", "zoneId": "sin"}`, + `{"pubId": "11244", "zoneId": "iad"}`, +} + +var invalidParams = []string{ + `{"pubId": "11233"}`, + `{"zoneId": "aaa"}`, + `{"pubId": 123, "zoneId": "sin"}`, + `{"pubId": "", "zoneId": "iad"}`, + `{"pubId": "11233", "zoneId": ""}`, +} diff --git a/adapters/yeahmobi/yeahmobi.go b/adapters/yeahmobi/yeahmobi.go new file mode 100644 index 00000000000..ede25387ce2 --- /dev/null +++ b/adapters/yeahmobi/yeahmobi.go @@ -0,0 +1,189 @@ +package yeahmobi + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "text/template" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + EndpointTemplate *template.Template +} + +// Builder builds a new instance of the Yeahmobi adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + EndpointTemplate: template, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var adapterRequests []*adapters.RequestData + + adapterRequest, errs := a.makeRequest(request) + if errs == nil { + adapterRequests = append(adapterRequests, adapterRequest) + } + + return adapterRequests, errs +} + +func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { + var errs []error + + yeahmobiExt, errs := getYeahmobiExt(request) + + if yeahmobiExt == nil { + return nil, errs + } + endPoint, err := a.getEndpoint(yeahmobiExt) + if err != nil { + return nil, append(errs, err) + } + transform(request) + reqBody, err := json.Marshal(request) + + if err != nil { + return nil, append(errs, err) + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + return &adapters.RequestData{ + Method: "POST", + Uri: endPoint, + Body: reqBody, + Headers: headers, + }, errs +} + +func transform(request *openrtb2.BidRequest) { + for i, imp := range request.Imp { + if imp.Native != nil { + var nativeRequest map[string]interface{} + nativeCopyRequest := make(map[string]interface{}) + err := json.Unmarshal([]byte(request.Imp[i].Native.Request), &nativeRequest) + //just ignore the bad native request + if err == nil { + _, exists := nativeRequest["native"] + if exists { + continue + } + + nativeCopyRequest["native"] = nativeRequest + nativeReqByte, err := json.Marshal(nativeCopyRequest) + //just ignore the bad native request + if err != nil { + continue + } + + nativeCopy := *request.Imp[i].Native + nativeCopy.Request = string(nativeReqByte) + request.Imp[i].Native = &nativeCopy + } + } + } +} + +func getYeahmobiExt(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpYeahmobi, []error) { + var extImpYeahmobi openrtb_ext.ExtImpYeahmobi + var errs []error + + for _, imp := range request.Imp { + var extBidder adapters.ExtImpBidder + err := json.Unmarshal(imp.Ext, &extBidder) + if err != nil { + errs = append(errs, err) + continue + } + err = json.Unmarshal(extBidder.Bidder, &extImpYeahmobi) + if err != nil { + errs = append(errs, err) + continue + } + break + } + + return &extImpYeahmobi, errs + +} + +func (a *adapter) getEndpoint(ext *openrtb_ext.ExtImpYeahmobi) (string, error) { + return macros.ResolveMacros(a.EndpointTemplate, macros.EndpointTemplateParams{Host: "gw-" + url.QueryEscape(ext.ZoneId) + "-bid.yeahtargeter.com"}) +} + +// MakeBids make the bids for the bid response. +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d.", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d.", response.StatusCode), + }} + } + + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + var mediaType = getBidType(sb.Bid[i].ImpID, internalRequest.Imp) + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: mediaType, + }) + } + } + return bidResponse, nil + +} + +func getBidType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { + bidType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impId { + if imp.Banner != nil { + break + } + if imp.Video != nil { + bidType = openrtb_ext.BidTypeVideo + break + } + if imp.Native != nil { + bidType = openrtb_ext.BidTypeNative + break + } + + } + } + return bidType +} diff --git a/adapters/yeahmobi/yeahmobi_test.go b/adapters/yeahmobi/yeahmobi_test.go new file mode 100644 index 00000000000..0b1c39ef214 --- /dev/null +++ b/adapters/yeahmobi/yeahmobi_test.go @@ -0,0 +1,28 @@ +package yeahmobi + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderYeahmobi, config.Adapter{ + Endpoint: "https://{{.Host}}/prebid/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "yeahmobitest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderYeahmobi, config.Adapter{ + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/no-bid.json b/adapters/yeahmobi/yeahmobitest/exemplary/no-bid.json new file mode 100644 index 00000000000..723cc40e664 --- /dev/null +++ b/adapters/yeahmobi/yeahmobitest/exemplary/no-bid.json @@ -0,0 +1,51 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] + +} diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-banner.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-banner.json new file mode 100644 index 00000000000..7499a7874e7 --- /dev/null +++ b/adapters/yeahmobi/yeahmobitest/exemplary/simple-banner.json @@ -0,0 +1,81 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some-ads", + "crid": "crid_testid" + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some-ads", + "crid": "crid_testid" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-native-1.1.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-native-1.1.json new file mode 100644 index 00000000000..7e93eb68246 --- /dev/null +++ b/adapters/yeahmobi/yeahmobitest/exemplary/simple-native-1.1.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"native\":{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"required\":1,\"img\":{\"type\":3,\"wmin\":128,\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"]}},{\"id\":7,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"native\":{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"required\":1,\"img\":{\"type\":3,\"wmin\":128,\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"]}},{\"id\":7,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "8400d766-58b3-47d4-80d7-6658b337d403", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some ads", + "crid": "crid_testid" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8400d766-58b3-47d4-80d7-6658b337d403", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some ads", + "crid": "crid_testid" + + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-native.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-native.json new file mode 100644 index 00000000000..894e835bc07 --- /dev/null +++ b/adapters/yeahmobi/yeahmobitest/exemplary/simple-native.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"required\":1,\"img\":{\"type\":3,\"wmin\":128,\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"]}},{\"id\":7,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"native\":{\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"img\":{\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"],\"type\":3,\"wmin\":128},\"required\":1},{\"data\":{\"len\":120,\"type\":2},\"id\":7,\"required\":1}],\"context\":1,\"plcmtcnt\":1,\"plcmttype\":4,\"ver\":\"1.2\"}}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "8400d766-58b3-47d4-80d7-6658b337d403", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some ads", + "crid": "crid_testid" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8400d766-58b3-47d4-80d7-6658b337d403", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some ads", + "crid": "crid_testid" + + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json new file mode 100644 index 00000000000..b040d31b5f6 --- /dev/null +++ b/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json @@ -0,0 +1,89 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some-ads", + "crid": "crid_testid" + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some-ads", + "crid": "crid_testid" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext.json b/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext.json new file mode 100644 index 00000000000..444e1e7a8d8 --- /dev/null +++ b/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext.json @@ -0,0 +1,21 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": "aaa" + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] +} diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext_bidder.json b/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext_bidder.json new file mode 100644 index 00000000000..89697d37141 --- /dev/null +++ b/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext_bidder.json @@ -0,0 +1,23 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": { + "bidder": "aa" + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpYeahmobi", + "comparison": "literal" + } + ] +} diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json b/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json new file mode 100644 index 00000000000..0d77e5af93a --- /dev/null +++ b/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "{\"id\":test-request-id" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "comparison": "literal", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse" + } + ] +} diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/status_400.json b/adapters/yeahmobi/yeahmobitest/supplemental/status_400.json new file mode 100644 index 00000000000..74bb869218c --- /dev/null +++ b/adapters/yeahmobi/yeahmobitest/supplemental/status_400.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "comparison": "literal", + "value": "Unexpected status code: 400." + } + ] +} diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/status_500.json b/adapters/yeahmobi/yeahmobitest/supplemental/status_500.json new file mode 100644 index 00000000000..2d3264de897 --- /dev/null +++ b/adapters/yeahmobi/yeahmobitest/supplemental/status_500.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gw-sin-bid.yeahtargeter.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "comparison": "literal", + "value": "Unexpected status code: 500." + } + ] +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index f758b3ee33b..ea9e99cfb6c 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -179,6 +179,7 @@ import ( "github.com/prebid/prebid-server/adapters/vrtcal" "github.com/prebid/prebid-server/adapters/xeworks" "github.com/prebid/prebid-server/adapters/yahooAds" + "github.com/prebid/prebid-server/adapters/yeahmobi" "github.com/prebid/prebid-server/adapters/yieldlab" "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/adapters/yieldone" @@ -389,6 +390,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderYahooAds: yahooAds.Builder, openrtb_ext.BidderYahooAdvertising: yahooAds.Builder, openrtb_ext.BidderYahooSSP: yahooAds.Builder, + openrtb_ext.BidderYeahmobi: yeahmobi.Builder, openrtb_ext.BidderYieldlab: yieldlab.Builder, openrtb_ext.BidderYieldmo: yieldmo.Builder, openrtb_ext.BidderYieldone: yieldone.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 1890635cb3d..a5a232bbd93 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -115,7 +115,6 @@ func GetDisabledBidderWarningMessages(infos config.BidderInfos) map[string]strin "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, "brightroll": `Bidder "brightroll" is no longer available in Prebid Server. Please update your configuration.`, "engagebdr": `Bidder "engagebdr" is no longer available in Prebid Server. Please update your configuration.`, - "yeahmobi": `Bidder "yeahmobi" is no longer available in Prebid Server. Please update your configuration.`, "ninthdecimal": `Bidder "ninthdecimal" is no longer available in Prebid Server. Please update your configuration.`, "kubient": `Bidder "kubient" is no longer available in Prebid Server. Please update your configuration.`, "definemedia": `Bidder "definemedia" is no longer available in Prebid Server. Please update your configuration.`, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 8b0262bca19..1caa625c0d1 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -215,6 +215,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderYahooAds, BidderYahooAdvertising, BidderYahooSSP, + BidderYeahmobi, BidderYieldlab, BidderYieldmo, BidderYieldone, @@ -505,6 +506,7 @@ const ( BidderYahooAds BidderName = "yahooAds" BidderYahooAdvertising BidderName = "yahooAdvertising" BidderYahooSSP BidderName = "yahoossp" + BidderYeahmobi BidderName = "yeahmobi" BidderYieldlab BidderName = "yieldlab" BidderYieldmo BidderName = "yieldmo" BidderYieldone BidderName = "yieldone" diff --git a/openrtb_ext/imp_yeahmobi.go b/openrtb_ext/imp_yeahmobi.go new file mode 100644 index 00000000000..16948d807c4 --- /dev/null +++ b/openrtb_ext/imp_yeahmobi.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpYeahmobi defines the contract for bidrequest.imp[i].ext.prebid.bidder.yeahmobi +type ExtImpYeahmobi struct { + PubId string `json:"pubId"` + ZoneId string `json:"zoneId"` +} diff --git a/static/bidder-info/yeahmobi.yaml b/static/bidder-info/yeahmobi.yaml new file mode 100644 index 00000000000..975bf044c5b --- /dev/null +++ b/static/bidder-info/yeahmobi.yaml @@ -0,0 +1,9 @@ +endpoint: "https://{{.Host}}/prebid/bid" +maintainer: + email: "developer@yeahmobi.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-params/yeahmobi.json b/static/bidder-params/yeahmobi.json new file mode 100644 index 00000000000..fe26fa7255a --- /dev/null +++ b/static/bidder-params/yeahmobi.json @@ -0,0 +1,21 @@ + +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Yeahmobi Adapter Params", + "description": "A schema which validates params accepted by the Yeahmobi adapter", + + "type": "object", + "properties": { + "pubId": { + "type": "string", + "description": "Publisher ID", + "minLength": 1 + }, + "zoneId": { + "type": "string", + "description": "Zone Id", + "minLength": 1 + } + }, + "required": ["pubId", "zoneId"] +} \ No newline at end of file From 11ef54d1989a624eb8c39e9b7183718fb82c67e0 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 18 Oct 2023 01:57:26 -0400 Subject: [PATCH 058/138] Adapter Name Case Insensitive: First Party Data (#3211) --- exchange/exchange.go | 1 + exchange/utils.go | 54 +++-- exchange/utils_test.go | 151 ++++++++++--- firstpartydata/first_party_data.go | 24 +-- firstpartydata/first_party_data_test.go | 198 +++++++++--------- .../bidder-config-case-normalize.json | 28 +++ ...er-config-duplicated-case-insensitive.json | 63 ++++++ ...r-config-request-alias-case-sensitive.json | 28 +++ .../global-fpd-defined-app-content-data.json | 2 +- ...-correct-case-insensitive-integration.json | 88 ++++++++ .../resolvefpd/bidder-fpd-alias-matches.json | 25 +++ .../bidder-fpd-alias-not-matches.json | 21 ++ .../resolvefpd/bidder-fpd-case-normalize.json | 25 +++ .../tests/resolvefpd/bidder-fpd-only-app.json | 55 ++--- .../resolvefpd/bidder-fpd-only-site.json | 55 ++--- .../resolvefpd/bidder-fpd-only-user.json | 65 +++--- .../bidder-fpd-site-content-data-only.json | 75 +++---- ...bidder-fpd-app-content-data-user-data.json | 77 +++---- .../resolvefpd/global-and-bidder-fpd-app.json | 62 +++--- .../global-and-bidder-fpd-site-user.json | 66 +++--- .../global-and-bidder-fpd-site.json | 52 ++--- .../global-and-bidder-fpd-user.json | 30 +-- .../global-and-bidder-site-content-data.json | 90 ++++---- ...lobal-and-no-bidder-fpd-site-app-user.json | 102 ++++----- ...d-no-bidder-fpd-site-content-app-user.json | 92 ++++---- .../tests/resolvefpd/global-fpd-only-app.json | 98 ++++----- .../global-fpd-only-excludes-some.json | 138 ++++++++++++ .../global-fpd-only-include-all-explicit.json | 144 +++++++++++++ .../global-fpd-only-include-all-implicit.json | 141 +++++++++++++ .../resolvefpd/global-fpd-only-site.json | 102 ++++----- .../req-and-bidder-fpd-site-content.json | 102 ++++----- .../tests/resolvefpd/req-app-not-defined.json | 6 +- .../resolvefpd/req-site-not-defined.json | 6 +- .../resolvefpd/req-user-not-defined.json | 10 +- .../resolvefpd/site-page-empty-conflict.json | 6 +- openrtb_ext/bidders.go | 9 + 36 files changed, 1593 insertions(+), 698 deletions(-) create mode 100644 firstpartydata/tests/extractbidderconfigfpd/bidder-config-case-normalize.json create mode 100644 firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated-case-insensitive.json create mode 100644 firstpartydata/tests/extractbidderconfigfpd/bidder-config-request-alias-case-sensitive.json create mode 100644 firstpartydata/tests/extractfpdforbidders/two-bidders-correct-case-insensitive-integration.json create mode 100644 firstpartydata/tests/resolvefpd/bidder-fpd-alias-matches.json create mode 100644 firstpartydata/tests/resolvefpd/bidder-fpd-alias-not-matches.json create mode 100644 firstpartydata/tests/resolvefpd/bidder-fpd-case-normalize.json create mode 100644 firstpartydata/tests/resolvefpd/global-fpd-only-excludes-some.json create mode 100644 firstpartydata/tests/resolvefpd/global-fpd-only-include-all-explicit.json create mode 100644 firstpartydata/tests/resolvefpd/global-fpd-only-include-all-implicit.json diff --git a/exchange/exchange.go b/exchange/exchange.go index 7544b4a7020..0810ad814ab 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -223,6 +223,7 @@ type BidderRequest struct { BidderCoreName openrtb_ext.BidderName BidderLabels metrics.AdapterLabels BidderStoredResponses map[string]json.RawMessage + IsRequestAlias bool ImpReplaceImpId map[string]bool } diff --git a/exchange/utils.go b/exchange/utils.go index 4fe9af12ff3..a67a87b9c97 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -214,9 +214,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, } - if auctionReq.FirstPartyData != nil && auctionReq.FirstPartyData[bidderRequest.BidderName] != nil { - applyFPD(auctionReq.FirstPartyData[bidderRequest.BidderName], bidderRequest.BidRequest) - } + applyFPD(auctionReq.FirstPartyData, bidderRequest) privacyEnforcement.TID = !auctionReq.Activities.Allow(privacy.ActivityTransmitTIDs, scopedName, privacy.NewRequestFromBidRequest(*req)) @@ -335,7 +333,7 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest, var errs []error for bidder, imps := range impsByBidder { - coreBidder := resolveBidder(bidder, aliases) + coreBidder, isRequestAlias := resolveBidder(bidder, aliases) reqCopy := *req.BidRequest reqCopy.Imp = imps @@ -355,6 +353,7 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest, bidderRequest := BidderRequest{ BidderName: openrtb_ext.BidderName(bidder), BidderCoreName: coreBidder, + IsRequestAlias: isRequestAlias, BidRequest: &reqCopy, BidderLabels: metrics.AdapterLabels{ Source: auctionRequest.LegacyLabels.Source, @@ -768,12 +767,14 @@ func setUserExtWithCopy(request *openrtb2.BidRequest, userExtJSON json.RawMessag } // resolveBidder returns the known BidderName associated with bidder, if bidder is an alias. If it's not an alias, the bidder is returned. -func resolveBidder(bidder string, aliases map[string]string) openrtb_ext.BidderName { +func resolveBidder(bidder string, requestAliases map[string]string) (openrtb_ext.BidderName, bool) { normalisedBidderName, _ := openrtb_ext.NormalizeBidderName(bidder) - if coreBidder, ok := aliases[bidder]; ok { - return openrtb_ext.BidderName(coreBidder) + + if coreBidder, ok := requestAliases[bidder]; ok { + return openrtb_ext.BidderName(coreBidder), true } - return normalisedBidderName + + return normalisedBidderName, false } // parseAliases parses the aliases from the BidRequest @@ -909,20 +910,36 @@ func getExtBidAdjustmentFactors(requestExtPrebid *openrtb_ext.ExtRequestPrebid) return nil } -func applyFPD(fpd *firstpartydata.ResolvedFirstPartyData, bidReq *openrtb2.BidRequest) { - if fpd.Site != nil { - bidReq.Site = fpd.Site +func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData, r BidderRequest) { + if fpd == nil { + return } - if fpd.App != nil { - bidReq.App = fpd.App + + bidder := r.BidderCoreName + if r.IsRequestAlias { + bidder = r.BidderName } - if fpd.User != nil { + + fpdToApply, exists := fpd[bidder] + if !exists || fpdToApply == nil { + return + } + + if fpdToApply.Site != nil { + r.BidRequest.Site = fpdToApply.Site + } + + if fpdToApply.App != nil { + r.BidRequest.App = fpdToApply.App + } + + if fpdToApply.User != nil { //BuyerUID is a value obtained between fpd extraction and fpd application. //BuyerUID needs to be set back to fpd before applying this fpd to final bidder request - if bidReq.User != nil && len(bidReq.User.BuyerUID) > 0 { - fpd.User.BuyerUID = bidReq.User.BuyerUID + if r.BidRequest.User != nil && len(r.BidRequest.User.BuyerUID) > 0 { + fpdToApply.User.BuyerUID = r.BidRequest.User.BuyerUID } - bidReq.User = fpd.User + r.BidRequest.User = fpdToApply.User } } @@ -934,13 +951,14 @@ func buildBidResponseRequest(req *openrtb2.BidRequest, bidderToBidderResponse := make(map[openrtb_ext.BidderName]BidderRequest) for bidderName, impResps := range bidderImpResponses { - resolvedBidder := resolveBidder(string(bidderName), aliases) + resolvedBidder, isRequestAlias := resolveBidder(string(bidderName), aliases) bidderToBidderResponse[bidderName] = BidderRequest{ BidRequest: req, BidderCoreName: resolvedBidder, BidderName: bidderName, BidderStoredResponses: impResps, ImpReplaceImpId: bidderImpReplaceImpID[string(resolvedBidder)], + IsRequestAlias: isRequestAlias, BidderLabels: metrics.AdapterLabels{Adapter: resolvedBidder}, } } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index a1e71b30c23..1be1aaa8493 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -3242,60 +3242,145 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { } func TestApplyFPD(t *testing.T) { - testCases := []struct { - description string - inputFpd firstpartydata.ResolvedFirstPartyData - inputRequest openrtb2.BidRequest - expectedRequest openrtb2.BidRequest + description string + inputFpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData + inputBidderName string + inputBidderCoreName string + inputBidderIsRequestAlias bool + inputRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest }{ { - description: "req.Site defined; bidderFPD.Site not defined; expect request.Site remains the same", - inputFpd: firstpartydata.ResolvedFirstPartyData{Site: nil, App: nil, User: nil}, - inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, - expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + description: "fpd-nil", + inputFpd: nil, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + }, + { + description: "fpd-bidderdata-nil", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": nil, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + }, + { + description: "fpd-bidderdata-notdefined", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "differentBidder": {App: &openrtb2.App{ID: "AppId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + }, + { + description: "fpd-bidderdata-alias", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "alias": {App: &openrtb2.App{ID: "AppId"}}, + }, + inputBidderName: "alias", + inputBidderCoreName: "bidder", + inputBidderIsRequestAlias: true, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + }, + { + description: "req.Site defined; bidderFPD.Site not defined; expect request.Site remains the same", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: nil, App: nil, User: nil}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, }, { description: "req.Site, req.App, req.User are not defined; bidderFPD.App, bidderFPD.Site and bidderFPD.User defined; " + "expect req.Site, req.App, req.User to be overriden by bidderFPD.App, bidderFPD.Site and bidderFPD.User", - inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, - inputRequest: openrtb2.BidRequest{}, - expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, }, { - description: "req.Site, defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App; expect req.Site remains the same", - inputFpd: firstpartydata.ResolvedFirstPartyData{App: &openrtb2.App{ID: "AppId"}}, - inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, - expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + description: "req.Site, defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App; expect req.Site remains the same", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {App: &openrtb2.App{ID: "AppId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, }, { - description: "req.Site, req.App defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App", - inputFpd: firstpartydata.ResolvedFirstPartyData{App: &openrtb2.App{ID: "AppId"}}, - inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "TestAppId"}}, - expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + description: "req.Site, req.App defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {App: &openrtb2.App{ID: "AppId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "TestAppId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, }, { - description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID. Expect to see user.BuyerUID in result request", - inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, - inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: "12345"}}, - expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", BuyerUID: "12345"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID. Expect to see user.BuyerUID in result request", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: "12345"}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", BuyerUID: "12345"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, }, { - description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID with zero length. Expect to see empty user.BuyerUID in result request", - inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, - inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: ""}}, - expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID with zero length. Expect to see empty user.BuyerUID in result request", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: ""}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, }, { - description: "req.User is not defined; bidderFPD.User defined and has BuyerUID. Expect to see user.BuyerUID in result request", - inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, - inputRequest: openrtb2.BidRequest{}, - expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, + description: "req.User is not defined; bidderFPD.User defined and has BuyerUID. Expect to see user.BuyerUID in result request", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, }, } for _, testCase := range testCases { - applyFPD(&testCase.inputFpd, &testCase.inputRequest) + bidderRequest := BidderRequest{ + BidderName: openrtb_ext.BidderName(testCase.inputBidderName), + BidderCoreName: openrtb_ext.BidderName(testCase.inputBidderCoreName), + IsRequestAlias: testCase.inputBidderIsRequestAlias, + BidRequest: &testCase.inputRequest, + } + applyFPD(testCase.inputFpd, bidderRequest) assert.Equal(t, testCase.expectedRequest, testCase.inputRequest, fmt.Sprintf("incorrect request after applying fpd, testcase %s", testCase.description)) } } diff --git a/firstpartydata/first_party_data.go b/firstpartydata/first_party_data.go index 0fde931d445..b63feb1ec52 100644 --- a/firstpartydata/first_party_data.go +++ b/firstpartydata/first_party_data.go @@ -120,7 +120,9 @@ func ResolveFPD(bidRequest *openrtb2.BidRequest, fpdBidderConfigData map[openrtb } } else { // only bidders in global bidder list will receive global data and bidder specific data - for _, bidderName := range biddersWithGlobalFPD { + for _, bidder := range biddersWithGlobalFPD { + bidderName := openrtb_ext.NormalizeBidderNameOrUnchanged(bidder) + if _, present := allBiddersTable[string(bidderName)]; !present { allBiddersTable[string(bidderName)] = struct{}{} } @@ -462,12 +464,14 @@ func buildExtData(data []byte) []byte { // ExtractBidderConfigFPD extracts bidder specific configs from req.ext.prebid.bidderconfig func ExtractBidderConfigFPD(reqExt *openrtb_ext.RequestExt) (map[openrtb_ext.BidderName]*openrtb_ext.ORTB2, error) { fpd := make(map[openrtb_ext.BidderName]*openrtb_ext.ORTB2) + reqExtPrebid := reqExt.GetPrebid() if reqExtPrebid != nil { for _, bidderConfig := range reqExtPrebid.BidderConfigs { for _, bidder := range bidderConfig.Bidders { - if _, present := fpd[openrtb_ext.BidderName(bidder)]; present { - //if bidder has duplicated config - throw an error + bidderName := openrtb_ext.NormalizeBidderNameOrUnchanged(bidder) + + if _, duplicate := fpd[bidderName]; duplicate { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("multiple First Party Data bidder configs provided for bidder: %s", bidder), } @@ -476,18 +480,12 @@ func ExtractBidderConfigFPD(reqExt *openrtb_ext.RequestExt) (map[openrtb_ext.Bid fpdBidderData := &openrtb_ext.ORTB2{} if bidderConfig.Config != nil && bidderConfig.Config.ORTB2 != nil { - if bidderConfig.Config.ORTB2.Site != nil { - fpdBidderData.Site = bidderConfig.Config.ORTB2.Site - } - if bidderConfig.Config.ORTB2.App != nil { - fpdBidderData.App = bidderConfig.Config.ORTB2.App - } - if bidderConfig.Config.ORTB2.User != nil { - fpdBidderData.User = bidderConfig.Config.ORTB2.User - } + fpdBidderData.Site = bidderConfig.Config.ORTB2.Site + fpdBidderData.App = bidderConfig.Config.ORTB2.App + fpdBidderData.User = bidderConfig.Config.ORTB2.User } - fpd[openrtb_ext.BidderName(bidder)] = fpdBidderData + fpd[bidderName] = fpdBidderData } } reqExtPrebid.BidderConfigs = nil diff --git a/firstpartydata/first_party_data_test.go b/firstpartydata/first_party_data_test.go index 4c9cd7ad5e8..ffa05a31d17 100644 --- a/firstpartydata/first_party_data_test.go +++ b/firstpartydata/first_party_data_test.go @@ -3,6 +3,7 @@ package firstpartydata import ( "encoding/json" "os" + "path/filepath" "reflect" "testing" @@ -474,13 +475,13 @@ func TestExtractBidderConfigFPD(t *testing.T) { for _, test := range tests { t.Run(test.Name(), func(t *testing.T) { - filePath := testPath + "/" + test.Name() + path := filepath.Join(testPath, test.Name()) - fpdFile, err := loadFpdFile(filePath) - require.NoError(t, err, "Cannot Load Test") + testFile, err := loadTestFile[fpdFile](path) + require.NoError(t, err, "Load Test File") givenRequestExtPrebid := &openrtb_ext.ExtRequestPrebid{} - err = json.Unmarshal(fpdFile.InputRequestData, givenRequestExtPrebid) + err = json.Unmarshal(testFile.InputRequestData, givenRequestExtPrebid) require.NoError(t, err, "Cannot Load Test Conditions") testRequest := &openrtb_ext.RequestExt{} @@ -490,15 +491,17 @@ func TestExtractBidderConfigFPD(t *testing.T) { results, err := ExtractBidderConfigFPD(testRequest) // assert errors - if len(fpdFile.ValidationErrors) > 0 { - require.EqualError(t, err, fpdFile.ValidationErrors[0].Message, "Expected Error Not Received") + if len(testFile.ValidationErrors) > 0 { + require.EqualError(t, err, testFile.ValidationErrors[0].Message, "Expected Error Not Received") } else { require.NoError(t, err, "Error Not Expected") assert.Nil(t, testRequest.GetPrebid().BidderConfigs, "Bidder specific FPD config should be removed from request") } // assert fpd (with normalization for nicer looking tests) - for bidderName, expectedFPD := range fpdFile.BidderConfigFPD { + for bidderName, expectedFPD := range testFile.BidderConfigFPD { + require.Contains(t, results, bidderName) + if expectedFPD.App != nil { assert.JSONEq(t, string(expectedFPD.App), string(results[bidderName].App), "app is incorrect") } else { @@ -520,7 +523,6 @@ func TestExtractBidderConfigFPD(t *testing.T) { }) } } - func TestResolveFPD(t *testing.T) { testPath := "tests/resolvefpd" @@ -529,31 +531,27 @@ func TestResolveFPD(t *testing.T) { for _, test := range tests { t.Run(test.Name(), func(t *testing.T) { - filePath := testPath + "/" + test.Name() + path := filepath.Join(testPath, test.Name()) - fpdFile, err := loadFpdFile(filePath) - require.NoError(t, err, "Cannot Load Test") + testFile, err := loadTestFile[fpdFileForResolveFPD](path) + require.NoError(t, err, "Load Test File") request := &openrtb2.BidRequest{} - err = json.Unmarshal(fpdFile.InputRequestData, &request) + err = json.Unmarshal(testFile.InputRequestData, &request) require.NoError(t, err, "Cannot Load Request") originalRequest := &openrtb2.BidRequest{} - err = json.Unmarshal(fpdFile.InputRequestData, &originalRequest) + err = json.Unmarshal(testFile.InputRequestData, &originalRequest) require.NoError(t, err, "Cannot Load Request") - outputReq := &openrtb2.BidRequest{} - err = json.Unmarshal(fpdFile.OutputRequestData, &outputReq) - require.NoError(t, err, "Cannot Load Output Request") - reqExtFPD := make(map[string][]byte) - reqExtFPD["site"] = fpdFile.GlobalFPD["site"] - reqExtFPD["app"] = fpdFile.GlobalFPD["app"] - reqExtFPD["user"] = fpdFile.GlobalFPD["user"] + reqExtFPD["site"] = testFile.GlobalFPD["site"] + reqExtFPD["app"] = testFile.GlobalFPD["app"] + reqExtFPD["user"] = testFile.GlobalFPD["user"] reqFPD := make(map[string][]openrtb2.Data, 3) - reqFPDSiteContentData := fpdFile.GlobalFPD[siteContentDataKey] + reqFPDSiteContentData := testFile.GlobalFPD[siteContentDataKey] if len(reqFPDSiteContentData) > 0 { var siteConData []openrtb2.Data err = json.Unmarshal(reqFPDSiteContentData, &siteConData) @@ -563,7 +561,7 @@ func TestResolveFPD(t *testing.T) { reqFPD[siteContentDataKey] = siteConData } - reqFPDAppContentData := fpdFile.GlobalFPD[appContentDataKey] + reqFPDAppContentData := testFile.GlobalFPD[appContentDataKey] if len(reqFPDAppContentData) > 0 { var appConData []openrtb2.Data err = json.Unmarshal(reqFPDAppContentData, &appConData) @@ -573,7 +571,7 @@ func TestResolveFPD(t *testing.T) { reqFPD[appContentDataKey] = appConData } - reqFPDUserData := fpdFile.GlobalFPD[userDataKey] + reqFPDUserData := testFile.GlobalFPD[userDataKey] if len(reqFPDUserData) > 0 { var userData []openrtb2.Data err = json.Unmarshal(reqFPDUserData, &userData) @@ -582,88 +580,88 @@ func TestResolveFPD(t *testing.T) { } reqFPD[userDataKey] = userData } - if fpdFile.BidderConfigFPD == nil { - fpdFile.BidderConfigFPD = make(map[openrtb_ext.BidderName]*openrtb_ext.ORTB2) - fpdFile.BidderConfigFPD["appnexus"] = &openrtb_ext.ORTB2{} - } // run test - resultFPD, errL := ResolveFPD(request, fpdFile.BidderConfigFPD, reqExtFPD, reqFPD, []string{"appnexus"}) + resultFPD, errL := ResolveFPD(request, testFile.BidderConfigFPD, reqExtFPD, reqFPD, testFile.BiddersWithGlobalFPD) if len(errL) == 0 { assert.Equal(t, request, originalRequest, "Original request should not be modified") - bidderFPD := resultFPD["appnexus"] - - if outputReq.Site != nil && len(outputReq.Site.Ext) > 0 { - resSiteExt := bidderFPD.Site.Ext - expectedSiteExt := outputReq.Site.Ext - bidderFPD.Site.Ext = nil - outputReq.Site.Ext = nil - assert.JSONEq(t, string(expectedSiteExt), string(resSiteExt), "site.ext is incorrect") - - assert.Equal(t, outputReq.Site, bidderFPD.Site, "Site is incorrect") + expectedResultKeys := []string{} + for k := range testFile.OutputRequestData { + expectedResultKeys = append(expectedResultKeys, k.String()) } - if outputReq.App != nil && len(outputReq.App.Ext) > 0 { - resAppExt := bidderFPD.App.Ext - expectedAppExt := outputReq.App.Ext - bidderFPD.App.Ext = nil - outputReq.App.Ext = nil - - assert.JSONEq(t, string(expectedAppExt), string(resAppExt), "app.ext is incorrect") - - assert.Equal(t, outputReq.App, bidderFPD.App, "App is incorrect") + actualResultKeys := []string{} + for k := range resultFPD { + actualResultKeys = append(actualResultKeys, k.String()) } - if outputReq.User != nil && len(outputReq.User.Ext) > 0 { - resUserExt := bidderFPD.User.Ext - expectedUserExt := outputReq.User.Ext - bidderFPD.User.Ext = nil - outputReq.User.Ext = nil - assert.JSONEq(t, string(expectedUserExt), string(resUserExt), "user.ext is incorrect") - - assert.Equal(t, outputReq.User, bidderFPD.User, "User is incorrect") + require.ElementsMatch(t, expectedResultKeys, actualResultKeys) + + for k, outputReq := range testFile.OutputRequestData { + bidderFPD := resultFPD[k] + + if outputReq.Site != nil && len(outputReq.Site.Ext) > 0 { + resSiteExt := bidderFPD.Site.Ext + expectedSiteExt := outputReq.Site.Ext + bidderFPD.Site.Ext = nil + outputReq.Site.Ext = nil + assert.JSONEq(t, string(expectedSiteExt), string(resSiteExt), "site.ext is incorrect") + assert.Equal(t, outputReq.Site, bidderFPD.Site, "Site is incorrect") + } + if outputReq.App != nil && len(outputReq.App.Ext) > 0 { + resAppExt := bidderFPD.App.Ext + expectedAppExt := outputReq.App.Ext + bidderFPD.App.Ext = nil + outputReq.App.Ext = nil + assert.JSONEq(t, string(expectedAppExt), string(resAppExt), "app.ext is incorrect") + assert.Equal(t, outputReq.App, bidderFPD.App, "App is incorrect") + } + if outputReq.User != nil && len(outputReq.User.Ext) > 0 { + resUserExt := bidderFPD.User.Ext + expectedUserExt := outputReq.User.Ext + bidderFPD.User.Ext = nil + outputReq.User.Ext = nil + assert.JSONEq(t, string(expectedUserExt), string(resUserExt), "user.ext is incorrect") + assert.Equal(t, outputReq.User, bidderFPD.User, "User is incorrect") + } } } else { - assert.ElementsMatch(t, errL, fpdFile.ValidationErrors, "Incorrect first party data warning message") + assert.ElementsMatch(t, errL, testFile.ValidationErrors, "Incorrect first party data warning message") } }) } } - func TestExtractFPDForBidders(t *testing.T) { if specFiles, err := os.ReadDir("./tests/extractfpdforbidders"); err == nil { for _, specFile := range specFiles { - fileName := "./tests/extractfpdforbidders/" + specFile.Name() - - fpdFile, err := loadFpdFile(fileName) + path := filepath.Join("./tests/extractfpdforbidders/", specFile.Name()) - if err != nil { - t.Errorf("Unable to load file: %s", fileName) - } + testFile, err := loadTestFile[fpdFile](path) + require.NoError(t, err, "Load Test File") var expectedRequest openrtb2.BidRequest - err = json.Unmarshal(fpdFile.OutputRequestData, &expectedRequest) + err = json.Unmarshal(testFile.OutputRequestData, &expectedRequest) if err != nil { - t.Errorf("Unable to unmarshal input request: %s", fileName) + t.Errorf("Unable to unmarshal input request: %s", path) } resultRequest := &openrtb_ext.RequestWrapper{} resultRequest.BidRequest = &openrtb2.BidRequest{} - err = json.Unmarshal(fpdFile.InputRequestData, resultRequest.BidRequest) + err = json.Unmarshal(testFile.InputRequestData, resultRequest.BidRequest) assert.NoError(t, err, "Error should be nil") resultFPD, errL := ExtractFPDForBidders(resultRequest) - if len(fpdFile.ValidationErrors) > 0 { - assert.Equal(t, len(fpdFile.ValidationErrors), len(errL), "Incorrect number of errors was returned") - assert.ElementsMatch(t, errL, fpdFile.ValidationErrors, "Incorrect errors were returned") + if len(testFile.ValidationErrors) > 0 { + assert.Equal(t, len(testFile.ValidationErrors), len(errL), "Incorrect number of errors was returned") + assert.ElementsMatch(t, errL, testFile.ValidationErrors, "Incorrect errors were returned") //in case or error no further assertions needed continue } assert.Empty(t, errL, "Error should be empty") - assert.Equal(t, len(resultFPD), len(fpdFile.BiddersFPDResolved)) + assert.Equal(t, len(resultFPD), len(testFile.BiddersFPDResolved)) - for bidderName, expectedValue := range fpdFile.BiddersFPDResolved { + for bidderName, expectedValue := range testFile.BiddersFPDResolved { actualValue := resultFPD[bidderName] if expectedValue.Site != nil { if len(expectedValue.Site.Ext) > 0 { @@ -715,34 +713,10 @@ func TestExtractFPDForBidders(t *testing.T) { } assert.Equal(t, expectedRequest.User, resultRequest.BidRequest.User, "Incorrect user in request") } - } } } -func loadFpdFile(filename string) (fpdFile, error) { - var fileData fpdFile - fileContents, err := os.ReadFile(filename) - if err != nil { - return fileData, err - } - err = json.Unmarshal(fileContents, &fileData) - if err != nil { - return fileData, err - } - - return fileData, nil -} - -type fpdFile struct { - InputRequestData json.RawMessage `json:"inputRequestData,omitempty"` - OutputRequestData json.RawMessage `json:"outputRequestData,omitempty"` - BidderConfigFPD map[openrtb_ext.BidderName]*openrtb_ext.ORTB2 `json:"bidderConfigFPD,omitempty"` - BiddersFPDResolved map[openrtb_ext.BidderName]*ResolvedFirstPartyData `json:"biddersFPDResolved,omitempty"` - GlobalFPD map[string]json.RawMessage `json:"globalFPD,omitempty"` - ValidationErrors []*errortypes.BadInput `json:"validationErrors,omitempty"` -} - func TestResolveUser(t *testing.T) { testCases := []struct { description string @@ -1927,3 +1901,37 @@ var ( } `) ) + +func loadTestFile[T any](filename string) (T, error) { + var testFile T + + b, err := os.ReadFile(filename) + if err != nil { + return testFile, err + } + + err = json.Unmarshal(b, &testFile) + if err != nil { + return testFile, err + } + + return testFile, nil +} + +type fpdFile struct { + InputRequestData json.RawMessage `json:"inputRequestData,omitempty"` + OutputRequestData json.RawMessage `json:"outputRequestData,omitempty"` + BidderConfigFPD map[openrtb_ext.BidderName]*openrtb_ext.ORTB2 `json:"bidderConfigFPD,omitempty"` + BiddersFPDResolved map[openrtb_ext.BidderName]*ResolvedFirstPartyData `json:"biddersFPDResolved,omitempty"` + GlobalFPD map[string]json.RawMessage `json:"globalFPD,omitempty"` + ValidationErrors []*errortypes.BadInput `json:"validationErrors,omitempty"` +} + +type fpdFileForResolveFPD struct { + InputRequestData json.RawMessage `json:"inputRequestData,omitempty"` + OutputRequestData map[openrtb_ext.BidderName]openrtb2.BidRequest `json:"outputRequestData,omitempty"` + BiddersWithGlobalFPD []string `json:"biddersWithGlobalFPD,omitempty"` + BidderConfigFPD map[openrtb_ext.BidderName]*openrtb_ext.ORTB2 `json:"bidderConfigFPD,omitempty"` + GlobalFPD map[string]json.RawMessage `json:"globalFPD,omitempty"` + ValidationErrors []*errortypes.BadInput `json:"validationErrors,omitempty"` +} diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-case-normalize.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-case-normalize.json new file mode 100644 index 00000000000..934afe47de0 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-case-normalize.json @@ -0,0 +1,28 @@ +{ + "description": "Extracts bidder configs for a bidder, normalizing the case for a known bidder", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "APPNexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId" + } + } + } + } + ] + }, + "outputRequestData": {}, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId" + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated-case-insensitive.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated-case-insensitive.json new file mode 100644 index 00000000000..ed9904579d1 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated-case-insensitive.json @@ -0,0 +1,63 @@ +{ + "description": "Verifies error presence in case more than one bidder config specified for the same bidder, case insensitive", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "sitefpddata": "sitefpddata", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata" + } + } + } + } + } + }, + { + "bidders": [ + "APPNEXUS", + "telaria", + "testBidder2" + ], + "config": { + "ortb2": { + "user": { + "id": "telariaUserData", + "ext": { + "data": { + "userdata": "fpduserdata" + } + } + }, + "app": { + "id": "telariaAppData", + "ext": { + "data": { + "appdata": "fpdappdata" + } + } + } + } + } + } + ] + }, + "outputRequestData": {}, + "bidderConfigFPD": {}, + "validationErrors": [ + { + "Message": "multiple First Party Data bidder configs provided for bidder: APPNEXUS" + } + ] +} \ No newline at end of file diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-request-alias-case-sensitive.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-request-alias-case-sensitive.json new file mode 100644 index 00000000000..1becf730ada --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-request-alias-case-sensitive.json @@ -0,0 +1,28 @@ +{ + "description": "Extracts bidder configs for a bidder, normalizing the case", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "requestAlias" + ], + "config": { + "ortb2": { + "site": { + "id": "aliasSiteId" + } + } + } + } + ] + }, + "outputRequestData": {}, + "bidderConfigFPD": { + "requestAlias": { + "site": { + "id": "aliasSiteId" + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json index 797846ee7a6..6e199f4eb0f 100644 --- a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json @@ -84,4 +84,4 @@ } }, "validationErrors": [] -} +} \ No newline at end of file diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-correct-case-insensitive-integration.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-correct-case-insensitive-integration.json new file mode 100644 index 00000000000..074c8ddef4e --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-correct-case-insensitive-integration.json @@ -0,0 +1,88 @@ +{ + "description": "case insensitive known bidder, case sensitive request alias", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "user": { + "id": "reqUserId" + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "APPNEXUS", + "requestAlias" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId" + }, + "user": { + "id": "apnUserId" + } + } + } + }, + { + "bidders": [ + "requestAlias" + ], + "config": { + "ortb2": { + "user": { + "keywords": "aliasUserKeywords" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "user": { + "id": "apnUserId" + } + }, + "requestAlias": { + "user": { + "id": "reqUserId", + "keywords": "aliasUserKeywords" + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-alias-matches.json b/firstpartydata/tests/resolvefpd/bidder-fpd-alias-matches.json new file mode 100644 index 00000000000..a9062d7ac4b --- /dev/null +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-alias-matches.json @@ -0,0 +1,25 @@ +{ + "description": "Bidder FPD defined with a case sensitive request alias, positive test", + "inputRequestData": { + "app": { + "id": "reqUserID" + } + }, + "biddersWithGlobalFPD": [ + "requestAlias" + ], + "bidderConfigFPD": { + "requestAlias": { + "app": { + "id": "apnAppId" + } + } + }, + "outputRequestData": { + "requestAlias": { + "app": { + "id": "apnAppId" + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-alias-not-matches.json b/firstpartydata/tests/resolvefpd/bidder-fpd-alias-not-matches.json new file mode 100644 index 00000000000..ffc549c3cef --- /dev/null +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-alias-not-matches.json @@ -0,0 +1,21 @@ +{ + "description": "Bidder FPD defined with a case sensitive request alias, negative test", + "inputRequestData": { + "app": { + "id": "reqUserID" + } + }, + "biddersWithGlobalFPD": [ + "requestAlias" + ], + "bidderConfigFPD": { + "REQUESTALIAS": { + "app": { + "id": "apnAppId" + } + } + }, + "outputRequestData": { + "requestAlias": {} + } +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-case-normalize.json b/firstpartydata/tests/resolvefpd/bidder-fpd-case-normalize.json new file mode 100644 index 00000000000..1f3e93b153c --- /dev/null +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-case-normalize.json @@ -0,0 +1,25 @@ +{ + "description": "Bidder FPD defined with a case insensitive bidder", + "inputRequestData": { + "app": { + "id": "reqUserID" + } + }, + "biddersWithGlobalFPD": [ + "APPNEXUS" + ], + "bidderConfigFPD": { + "appnexus": { + "app": { + "id": "apnAppId" + } + } + }, + "outputRequestData": { + "appnexus": { + "app": { + "id": "apnAppId" + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json b/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json index 812db7b10b5..7dc9adee182 100644 --- a/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json @@ -1,30 +1,35 @@ { - "description": "Bidder FPD defined only for app", - "inputRequestData": { - "app": { - "id": "reqUserID" - } - }, - "bidderConfigFPD": { - "appnexus": { - "app": { - "id": "apnAppId", - "ext": { - "data": { - "other": "data" - } - } - } + "description": "Bidder FPD defined only for app", + "inputRequestData": { + "app": { + "id": "reqUserID" + } + }, + "biddersWithGlobalFPD": [ + "appnexus" + ], + "bidderConfigFPD": { + "appnexus": { + "app": { + "id": "apnAppId", + "ext": { + "data": { + "other": "data" + } } - }, - "outputRequestData": { - "app": { - "id": "apnAppId", - "ext": { - "data": { - "other": "data" - } - } + } + } + }, + "outputRequestData": { + "appnexus": { + "app": { + "id": "apnAppId", + "ext": { + "data": { + "other": "data" + } } + } } + } } \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json b/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json index 5accfc3b3a0..8ee3b40175b 100644 --- a/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json @@ -1,30 +1,35 @@ { - "description": "Bidder FPD defined only for site", - "inputRequestData": { - "site": { - "id": "reqUserID" - } - }, - "bidderConfigFPD": { - "appnexus": { - "site": { - "id": "apnSiteId", - "ext": { - "data": { - "other": "data" - } - } - } + "description": "Bidder FPD defined only for site", + "inputRequestData": { + "site": { + "id": "reqUserID" + } + }, + "biddersWithGlobalFPD": [ + "appnexus" + ], + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "other": "data" + } } - }, - "outputRequestData": { - "site": { - "id": "apnSiteId", - "ext": { - "data": { - "other": "data" - } - } + } + } + }, + "outputRequestData": { + "appnexus": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "other": "data" + } } + } } + } } \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json b/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json index 606cee7dbe6..204cf3c8d3f 100644 --- a/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json @@ -1,35 +1,40 @@ { - "description": "Bidder FPD defined only for user", - "inputRequestData": { - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M" - } - }, - "bidderConfigFPD": { - "appnexus": { - "user": { - "id": "apnUserId", - "yob": 1982, - "ext": { - "data": { - "other": "data" - } - } - } + "description": "Bidder FPD defined only for user", + "inputRequestData": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "biddersWithGlobalFPD": [ + "appnexus" + ], + "bidderConfigFPD": { + "appnexus": { + "user": { + "id": "apnUserId", + "yob": 1982, + "ext": { + "data": { + "other": "data" + } } - }, - "outputRequestData": { - "user": { - "id": "apnUserId", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "other": "data" - } - } + } + } + }, + "outputRequestData": { + "appnexus": { + "user": { + "id": "apnUserId", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "other": "data" + } } + } } + } } \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-site-content-data-only.json b/firstpartydata/tests/resolvefpd/bidder-fpd-site-content-data-only.json index 541d51f42af..2008d46f265 100644 --- a/firstpartydata/tests/resolvefpd/bidder-fpd-site-content-data-only.json +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-site-content-data-only.json @@ -28,6 +28,9 @@ "ifa": "123" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -60,44 +63,46 @@ } }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "name": "apnSiteName", - "domain": "apnSiteDomain", - "page": "http://www.foobar.com/1234.html", - "cat": [ - "books", - "novels" - ], - "search": "book search", - "publisher": { - "id": "1" - }, - "content": { - "episode": 7, - "title": "apnEpisodeName", - "series": "TvName", - "season": "season3", - "len": 600, - "data": [ - { - "id": "siteData3", - "name": "siteName3" + "appnexus": { + "site": { + "id": "apnSiteId", + "name": "apnSiteName", + "domain": "apnSiteDomain", + "page": "http://www.foobar.com/1234.html", + "cat": [ + "books", + "novels" + ], + "search": "book search", + "publisher": { + "id": "1" + }, + "content": { + "episode": 7, + "title": "apnEpisodeName", + "series": "TvName", + "season": "season3", + "len": 600, + "data": [ + { + "id": "siteData3", + "name": "siteName3" + } + ] + }, + "ext": { + "data": { + "other": "data", + "testSiteFpd": "testSite" } - ] - }, - "ext": { - "data": { - "other": "data", - "testSiteFpd": "testSite" } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" } } } \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json index 16b5332e942..7be277eb5ff 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json @@ -14,6 +14,9 @@ "gender": "reqUserGender" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "app": { @@ -55,48 +58,50 @@ ] }, "outputRequestData": { - "app": { - "id": "apnAppId", - "page": "http://www.foobar.com/1234.html", - "publisher": { - "id": "1" + "appnexus": { + "app": { + "id": "apnAppId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "data": [ + { + "id": "appData1", + "name": "appName1" + }, + { + "id": "appData2", + "name": "appName2" + } + ] + }, + "ext": { + "data": { + "morefpdData": "morefpddata", + "appFpd": 123 + } + } }, - "content": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "reqUserGender", "data": [ { - "id": "appData1", - "name": "appName1" + "id": "userData1", + "name": "userName1" }, { - "id": "appData2", - "name": "appName2" + "id": "userData2", + "name": "userName2" + } + ], + "ext": { + "data": { + "testUserFpd": "testuser" } - ] - }, - "ext": { - "data": { - "morefpdData": "morefpddata", - "appFpd": 123 - } - } - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "reqUserGender", - "data": [ - { - "id": "userData1", - "name": "userName1" - }, - { - "id": "userData2", - "name": "userName2" - } - ], - "ext": { - "data": { - "testUserFpd": "testuser" } } } diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json index 609ede597cf..fb27d1631c1 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json @@ -22,6 +22,9 @@ "ifa": "123" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "app": { @@ -44,36 +47,37 @@ } }, "outputRequestData": { - "app": { - "id": "apnAppId", - "page": "http://www.foobar.com/1234.html", - "publisher": { - "id": "1" - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900 - }, - "ext": { - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "appFpd": 123, - "appFpddata": "appFpddata" + "appnexus": { + "app": { + "id": "apnAppId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "appFpd": 123, + "appFpddata": "appFpddata" + } } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json index f66a8722308..9563fffebcc 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json @@ -19,6 +19,9 @@ "id": "apnUserId" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -51,40 +54,41 @@ } }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "publisher": { - "id": "1" - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900 - }, - "ext": { - "data": { - "morefpdData": "morefpddata", - "siteFpddata": "siteFpddata", - "moreFpd": { - "fpd": 123 + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } } } - } - }, - "user": { - "id": "apnUserId", - "ext": { - "data": { - "moreFpd": { - "fpd": 567 - }, - "testUserFpd": "testuser" + }, + "user": { + "id": "apnUserId", + "ext": { + "data": { + "moreFpd": { + "fpd": 567 + }, + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json index be03f0cb9b0..4362362718a 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json @@ -16,6 +16,9 @@ } } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -40,31 +43,32 @@ } }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "ref": "fpdRef", - "publisher": { - "id": "1" - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900 - }, - "ext": { - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "siteFpd": 123, - "siteFpddata": "siteFpddata" + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json index ef381d613e6..4d233e6c473 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json @@ -7,6 +7,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "user": { @@ -30,21 +33,22 @@ } }, "outputRequestData": { - "user": { - "id": "apnUserId", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser", - "morefpdData": "morefpddata", - "userFpddata": "siteFpddata", - "moreFpd": { - "fpd": 123 + "appnexus": { + "user": { + "id": "apnUserId", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser", + "morefpdData": "morefpddata", + "userFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json b/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json index c0f4723d62a..a0a1c461292 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json @@ -20,6 +20,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -62,52 +65,53 @@ ] }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "ref": "fpdRef", - "publisher": { - "id": "1" - }, - "content": { - "data": [ - { - "id": "siteData1", - "name": "siteName1" - }, - { - "id": "siteData2", - "name": "siteName2" + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" } - ] - }, - "ext": { - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "siteFpd": 123, - "siteFpddata": "siteFpddata" } - } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser" + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-app-user.json b/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-app-user.json index a705e8b2405..e8dace465e7 100644 --- a/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-app-user.json +++ b/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-app-user.json @@ -23,6 +23,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -72,58 +75,59 @@ ] }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "ref": "fpdRef", - "publisher": { - "id": "1" - }, - "content": { - "title": "episodeName8", - "series": "TvName", - "season": "season4", - "episode": 8, - "len": 900, - "data": [ - { - "id": "siteData1", - "name": "siteName1" - }, - { - "id": "siteData2", - "name": "siteName2" + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "title": "episodeName8", + "series": "TvName", + "season": "season4", + "episode": 8, + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" } - ] - }, - "ext": { - "testSiteExt": 123, - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "siteFpd": 123, - "siteFpddata": "siteFpddata" } - } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser" + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-content-app-user.json b/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-content-app-user.json index ccfa030cd8d..10fa471744f 100644 --- a/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-content-app-user.json +++ b/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-content-app-user.json @@ -23,6 +23,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -65,53 +68,54 @@ ] }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "ref": "fpdRef", - "publisher": { - "id": "1" - }, - "content": { - "data": [ - { - "id": "siteData1", - "name": "siteName1" - }, - { - "id": "siteData2", - "name": "siteName2" + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" } - ] - }, - "ext": { - "testSiteExt": 123, - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "siteFpd": 123, - "siteFpddata": "siteFpddata" } - } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser" + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-fpd-only-app.json b/firstpartydata/tests/resolvefpd/global-fpd-only-app.json index e4d5e169986..55839983a5b 100644 --- a/firstpartydata/tests/resolvefpd/global-fpd-only-app.json +++ b/firstpartydata/tests/resolvefpd/global-fpd-only-app.json @@ -29,6 +29,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "app": { @@ -64,56 +67,57 @@ ] }, "outputRequestData": { - "app": { - "id": "apnAppId", - "publisher": { - "id": "1" - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900, - "data": [ - { - "id": "appData1", - "name": "appName1" - }, - { - "id": "appData2", - "name": "appName2" + "appnexus": { + "app": { + "id": "apnAppId", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "appData1", + "name": "appName1" + }, + { + "id": "appData2", + "name": "appName2" + } + ] + }, + "ext": { + "testAppExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "appFpd": 123, + "appFpddata": "appFpddata" } - ] - }, - "ext": { - "testAppExt": 123, - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "appFpd": 123, - "appFpddata": "appFpddata" } - } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser" + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-fpd-only-excludes-some.json b/firstpartydata/tests/resolvefpd/global-fpd-only-excludes-some.json new file mode 100644 index 00000000000..b23a347cf9b --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-fpd-only-excludes-some.json @@ -0,0 +1,138 @@ +{ + "description": "Global and bidder FPD defined for site and user. Global FPD has site.content.data. Excludes rubicon since not listed in biddersWithGlobalFPD", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "testSiteExt": 123 + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "biddersWithGlobalFPD": [ + "appnexus" + ], + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + }, + "rubicon": { + "site": { + "id": "rbcSiteId" + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + }, + "app": { + "appFpd": { + "testValue": true + } + }, + "user": { + "testUserFpd": "testuser" + }, + "siteContentData": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "outputRequestData": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } + } + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-fpd-only-include-all-explicit.json b/firstpartydata/tests/resolvefpd/global-fpd-only-include-all-explicit.json new file mode 100644 index 00000000000..f959ebcde4f --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-fpd-only-include-all-explicit.json @@ -0,0 +1,144 @@ +{ + "description": "Global and bidder FPD defined for site and user. Global FPD has site.content.data. Bidders mentioned explicitly in biddersWithGlobalFPD", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "testSiteExt": 123 + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "biddersWithGlobalFPD": [ + "appnexus", + "rubicon" + ], + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + }, + "rubicon": { + "site": { + "id": "rbcSiteId" + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + }, + "app": { + "appFpd": { + "testValue": true + } + }, + "user": { + "testUserFpd": "testuser" + }, + "siteContentData": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "outputRequestData": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } + } + } + }, + "rubicon": { + "site": { + "id": "rbcSiteId" + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-fpd-only-include-all-implicit.json b/firstpartydata/tests/resolvefpd/global-fpd-only-include-all-implicit.json new file mode 100644 index 00000000000..7c1f0f10aff --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-fpd-only-include-all-implicit.json @@ -0,0 +1,141 @@ +{ + "description": "Global and bidder FPD defined for site and user. Global FPD has site.content.data. Bidders included implicitly with biddersWithGlobalFPD nil", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "testSiteExt": 123 + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "biddersWithGlobalFPD": null, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + }, + "rubicon": { + "site": { + "id": "rbcSiteId" + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + }, + "app": { + "appFpd": { + "testValue": true + } + }, + "user": { + "testUserFpd": "testuser" + }, + "siteContentData": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "outputRequestData": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } + } + } + }, + "rubicon": { + "site": { + "id": "rbcSiteId" + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-fpd-only-site.json b/firstpartydata/tests/resolvefpd/global-fpd-only-site.json index 49b8f4dcefb..3f7e91adc63 100644 --- a/firstpartydata/tests/resolvefpd/global-fpd-only-site.json +++ b/firstpartydata/tests/resolvefpd/global-fpd-only-site.json @@ -30,6 +30,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -72,58 +75,59 @@ ] }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "ref": "fpdRef", - "publisher": { - "id": "1" - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900, - "data": [ - { - "id": "siteData1", - "name": "siteName1" - }, - { - "id": "siteData2", - "name": "siteName2" + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" } - ] - }, - "ext": { - "testSiteExt": 123, - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "siteFpd": 123, - "siteFpddata": "siteFpddata" } - } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser" + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/req-and-bidder-fpd-site-content.json b/firstpartydata/tests/resolvefpd/req-and-bidder-fpd-site-content.json index 446e7151afa..da0d4357dd1 100644 --- a/firstpartydata/tests/resolvefpd/req-and-bidder-fpd-site-content.json +++ b/firstpartydata/tests/resolvefpd/req-and-bidder-fpd-site-content.json @@ -40,6 +40,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -89,58 +92,59 @@ } }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "ref": "fpdRef", - "publisher": { - "id": "1" - }, - "content": { - "episode": 8, - "title": "episodeName8", - "series": "TvName", - "season": "season4", - "len": 900, - "data": [ - { - "id": "siteData1", - "name": "siteName1" - }, - { - "id": "siteData2", - "name": "siteName2" + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 8, + "title": "episodeName8", + "series": "TvName", + "season": "season4", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" } - ] - }, - "ext": { - "testSiteExt": 123, - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "siteFpd": 123, - "siteFpddata": "siteFpddata" } - } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser" + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/req-app-not-defined.json b/firstpartydata/tests/resolvefpd/req-app-not-defined.json index f24a4d27db1..6ab9e076f43 100644 --- a/firstpartydata/tests/resolvefpd/req-app-not-defined.json +++ b/firstpartydata/tests/resolvefpd/req-app-not-defined.json @@ -20,6 +20,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "app": { @@ -42,5 +45,4 @@ "Message": "incorrect First Party Data for bidder appnexus: App object is not defined in request, but defined in FPD config" } ] -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/req-site-not-defined.json b/firstpartydata/tests/resolvefpd/req-site-not-defined.json index d078ad7804f..8e20a6e0e1d 100644 --- a/firstpartydata/tests/resolvefpd/req-site-not-defined.json +++ b/firstpartydata/tests/resolvefpd/req-site-not-defined.json @@ -7,6 +7,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -29,5 +32,4 @@ "Message": "incorrect First Party Data for bidder appnexus: Site object is not defined in request, but defined in FPD config" } ] -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/req-user-not-defined.json b/firstpartydata/tests/resolvefpd/req-user-not-defined.json index 76ae3f827ca..85c41c23969 100644 --- a/firstpartydata/tests/resolvefpd/req-user-not-defined.json +++ b/firstpartydata/tests/resolvefpd/req-user-not-defined.json @@ -5,6 +5,9 @@ "at": 1, "tmax": 5000 }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "user": { @@ -21,11 +24,12 @@ } } }, - "outputRequestData": {}, + "outputRequestData": { + "appnexus": {} + }, "validationErrors": [ { "Message": "incorrect First Party Data for bidder appnexus: User object is not defined in request, but defined in FPD config" } ] -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json b/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json index 8da3dc69329..260385c49be 100644 --- a/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json +++ b/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json @@ -19,6 +19,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -41,5 +44,4 @@ "Message": "incorrect First Party Data for bidder appnexus: Site object cannot set empty page if req.site.id is empty" } ] -} - +} \ No newline at end of file diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 1caa625c0d1..2258cad4214 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -563,6 +563,15 @@ func NormalizeBidderName(name string) (BidderName, bool) { return bidderName, exists } +// NormalizeBidderNameOrUnchanged returns the normalized name of known bidders, otherwise returns +// the name exactly as provided. +func NormalizeBidderNameOrUnchanged(name string) BidderName { + if normalized, exists := NormalizeBidderName(name); exists { + return normalized + } + return BidderName(name) +} + // The BidderParamValidator is used to enforce bidrequest.imp[i].ext.prebid.bidder.{anyBidder} values. // // This is treated differently from the other types because we rely on JSON-schemas to validate bidder params. From bde7bcf096b388186c183d3282b0c3bb75aeddaf Mon Sep 17 00:00:00 2001 From: guscarreon Date: Wed, 18 Oct 2023 02:19:04 -0400 Subject: [PATCH 059/138] Adapter Name Case Insensitive: ext.prebid.multibid (#3217) --- exchange/exchange.go | 8 +- exchange/exchange_test.go | 197 ++++++++ .../exchangetest/multi-bids-mixed-case.json | 449 ++++++++++++++++++ exchange/utils.go | 4 +- 4 files changed, 654 insertions(+), 4 deletions(-) create mode 100644 exchange/exchangetest/multi-bids-mixed-case.json diff --git a/exchange/exchange.go b/exchange/exchange.go index 0810ad814ab..a8c76a5ebb1 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -515,10 +515,14 @@ func buildMultiBidMap(prebid *openrtb_ext.ExtRequestPrebid) map[string]openrtb_e multiBidMap := make(map[string]openrtb_ext.ExtMultiBid) for _, multiBid := range prebid.MultiBid { if multiBid.Bidder != "" { - multiBidMap[multiBid.Bidder] = *multiBid + if bidderNormalized, bidderFound := openrtb_ext.NormalizeBidderName(multiBid.Bidder); bidderFound { + multiBidMap[string(bidderNormalized)] = *multiBid + } } else { for _, bidder := range multiBid.Bidders { - multiBidMap[bidder] = *multiBid + if bidderNormalized, bidderFound := openrtb_ext.NormalizeBidderName(bidder); bidderFound { + multiBidMap[string(bidderNormalized)] = *multiBid + } } } } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 6e6e8afe76e..e57900cdfee 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2259,8 +2259,13 @@ func TestTimeoutComputation(t *testing.T) { func TestExchangeJSON(t *testing.T) { if specFiles, err := os.ReadDir("./exchangetest"); err == nil { for _, specFile := range specFiles { + if !strings.HasSuffix(specFile.Name(), ".json") { + continue + } + fileName := "./exchangetest/" + specFile.Name() fileDisplayName := "exchange/exchangetest/" + specFile.Name() + t.Run(fileDisplayName, func(t *testing.T) { specData, err := loadFile(fileName) if assert.NoError(t, err, "Failed to load contents of file %s: %v", fileDisplayName, err) { @@ -5750,3 +5755,195 @@ func TestSetSeatNonBid(t *testing.T) { }) } } + +func TestBuildMultiBidMap(t *testing.T) { + type testCase struct { + desc string + inPrebid *openrtb_ext.ExtRequestPrebid + expected map[string]openrtb_ext.ExtMultiBid + } + testGroups := []struct { + groupDesc string + tests []testCase + }{ + { + groupDesc: "Nil or empty tests", + tests: []testCase{ + { + desc: "prebid nil, expect nil map", + inPrebid: nil, + expected: nil, + }, + { + desc: "prebid.MultiBid nil, expect nil map", + inPrebid: &openrtb_ext.ExtRequestPrebid{}, + expected: nil, + }, + { + desc: "not-nil prebid.MultiBid is empty, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{}, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + }, + }, + { + groupDesc: "prebid.MultiBid.Bidder tests", + tests: []testCase{ + { + desc: "Lowercase prebid.MultiBid.Bidder is found in the BidderName list, entry is mapped", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "appnexus"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": {Bidder: "appnexus"}, + }, + }, + { + desc: "Uppercase prebid.MultiBid.Bidder is found in the BidderName list, entry is mapped", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "APPNEXUS"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": {Bidder: "APPNEXUS"}, + }, + }, + { + desc: "Lowercase prebid.MultiBid.Bidder is not found in the BidderName list, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "unknown"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "Mixed-case prebid.MultiBid.Bidder is not found in the BidderName list, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "UnknownBidder"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "Different-cased prebid.MultiBid.Bidder entries that refer to the same adapter are found in the BidderName list are mapped once", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "AppNexus"}, + {Bidder: "appnexus"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": {Bidder: "appnexus"}, + }, + }, + }, + }, + { + groupDesc: "prebid.MultiBid.Bidders tests", + tests: []testCase{ + { + desc: "Lowercase prebid.MultiBid.Bidder is found in the BidderName list, entry is mapped", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidders: []string{"appnexus"}}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidders: []string{"appnexus"}, + }, + }, + }, + { + desc: "Lowercase prebid.MultiBid.Bidder is not found in the BidderName list, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidders: []string{"unknown"}}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "Mixed-case prebid.MultiBid.Bidder is not found in the BidderName list, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidders: []string{"UnknownBidder"}}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "Different-cased prebid.MultiBid.Bidder entries that refer to the same adapter are found in the BidderName list are mapped once", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidders: []string{"AppNexus", "appnexus", "UnknownBidder"}}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidders: []string{"AppNexus", "appnexus", "UnknownBidder"}, + }, + }, + }, + }, + }, + { + groupDesc: "prebid.MultiBid.Bidder and prebid.MultiBid.Bidders entries in tests", + tests: []testCase{ + { + desc: "prebid.MultiBid.Bidder found, ignore entries in prebid.MultiBid.Bidders, even if its unknown", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + { + Bidder: "UnknownBidder", + Bidders: []string{"appnexus", "rubicon", "pubmatic"}, + }, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "prebid.MultiBid.Bidder found in one entry, prebid.MultiBid.Bidders in another. Add all to map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + { + Bidder: "pubmatic", + Bidders: []string{"appnexus", "rubicon", "UnknownBidder"}, + }, + { + Bidders: []string{"UnknownBidder", "appnexus", "rubicon"}, + }, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "pubmatic": { + Bidder: "pubmatic", + Bidders: []string{"appnexus", "rubicon", "UnknownBidder"}, + }, + "appnexus": { + Bidders: []string{"UnknownBidder", "appnexus", "rubicon"}, + }, + "rubicon": { + Bidders: []string{"UnknownBidder", "appnexus", "rubicon"}, + }, + }, + }, + }, + }, + } + for _, group := range testGroups { + for _, tc := range group.tests { + t.Run(group.groupDesc+tc.desc, func(t *testing.T) { + multiBidMap := buildMultiBidMap(tc.inPrebid) + assert.Equal(t, tc.expected, multiBidMap, tc.desc) + }) + } + } +} diff --git a/exchange/exchangetest/multi-bids-mixed-case.json b/exchange/exchangetest/multi-bids-mixed-case.json new file mode 100644 index 00000000000..44630b3068c --- /dev/null +++ b/exchange/exchangetest/multi-bids-mixed-case.json @@ -0,0 +1,449 @@ +{ + "description": "incoming req.ext.prebid.multibid comes with a mixed case bidder name. Expect the same bidder name casing in the outgoing ext.prebid.multibid field", + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pubmatic": { + "publisherId": "5890" + }, + "appnexus": { + "placementId": 1 + } + } + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pubmatic": { + "publisherId": "5890" + }, + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": true, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "min": 0, + "max": 20, + "increment": 0.1 + } + ] + } + }, + "multibid": [ + { + "bidder": "PUBmatic", + "maxbids": 2, + "targetbiddercodeprefix": "pubm" + }, + { + "bidders": [ + "appnexus", + "someBidder" + ], + "maxbids": 2 + } + ] + } + } + } + }, + "outgoingRequests": { + "pubmatic": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "5890" + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "5890" + } + } + } + ], + "ext": { + "prebid": { + "multibid": [ + { + "bidder": "PUBmatic", + "maxbids": 2, + "targetbiddercodeprefix": "pubm" + } + ] + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + }, + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + }, + { + "ortbBid": { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + }, + { + "ortbBid": { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + } + ], + "seat": "pubmatic" + } + ] + } + }, + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "multibid": [ + { + "bidders": [ + "appnexus" + ], + "maxbids": 2 + } + ] + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-1" + }, + "bidType": "banner", + "bidMeta": { + "adaptercode": "appnexus" + } + }, + { + "ortbBid": { + "id": "apn-bid-2", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-2" + }, + "bidType": "banner", + "bidMeta": { + "adaptercode": "appnexus" + } + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "pubmatic", + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.71, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video", + "targeting": { + "hb_bidder": "pubmatic", + "hb_bidder_pubmatic": "pubmatic", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_pubmat": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_pubmat": "/pbcache/endpoint", + "hb_pb": "0.70", + "hb_pb_pubmatic": "0.70", + "hb_size": "200x250", + "hb_size_pubmatic": "200x250" + }, + "targetbiddercode": "pubmatic" + } + } + }, + { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2", + "ext": { + "origbidcpm": 0.21, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video" + } + } + }, + { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3", + "ext": { + "origbidcpm": 0.61, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video", + "targeting": { + "hb_bidder": "pubmatic", + "hb_bidder_pubmatic": "pubmatic", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_pubmat": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_pubmat": "/pbcache/endpoint", + "hb_pb": "0.60", + "hb_pb_pubmatic": "0.60", + "hb_size": "300x500", + "hb_size_pubmatic": "300x500" + }, + "targetbiddercode": "pubmatic" + } + } + }, + { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4", + "ext": { + "origbidcpm": 0.51, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video", + "targeting": { + "hb_bidder_pubm2": "pubm2", + "hb_cache_host_pubm2": "www.pbcserver.com", + "hb_cache_path_pubm2": "/pbcache/endpoint", + "hb_pb_pubm2": "0.50", + "hb_size_pubm2": "200x250" + }, + "targetbiddercode": "pubm2" + } + } + } + ] + }, + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "targetbiddercode": "appnexus", + "type": "banner", + "targeting": { + "hb_bidder_appnexus": "appnexus", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb_appnexus": "0.20", + "hb_size_appnexus": "200x500" + } + } + } + }, + { + "id": "apn-bid-2", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-2", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/utils.go b/exchange/utils.go index a67a87b9c97..70988fbf7b9 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -472,12 +472,12 @@ func buildRequestExtMultiBid(adapter string, reqMultiBid []*openrtb_ext.ExtMulti adapterMultiBid := make([]*openrtb_ext.ExtMultiBid, 0) for _, multiBid := range reqMultiBid { if multiBid.Bidder != "" { - if multiBid.Bidder == adapter || isBidderInExtAlternateBidderCodes(adapter, multiBid.Bidder, adapterABC) { + if strings.ToLower(multiBid.Bidder) == adapter || isBidderInExtAlternateBidderCodes(adapter, strings.ToLower(multiBid.Bidder), adapterABC) { adapterMultiBid = append(adapterMultiBid, multiBid) } } else { for _, bidder := range multiBid.Bidders { - if bidder == adapter || isBidderInExtAlternateBidderCodes(adapter, bidder, adapterABC) { + if strings.ToLower(bidder) == adapter || isBidderInExtAlternateBidderCodes(adapter, strings.ToLower(bidder), adapterABC) { adapterMultiBid = append(adapterMultiBid, &openrtb_ext.ExtMultiBid{ Bidders: []string{bidder}, MaxBids: multiBid.MaxBids, From f4425b6dbbf5e3f85adf9e377e5eec55afc9fc74 Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Wed, 18 Oct 2023 18:23:49 +0530 Subject: [PATCH 060/138] migrate janet alias to use new pattern (#3189) --- exchange/adapter_builders.go | 1 - openrtb_ext/bidders.go | 2 -- static/bidder-info/janet.yaml | 10 +--------- static/bidder-params/janet.json | 26 -------------------------- 4 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 static/bidder-params/janet.json diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index ea9e99cfb6c..573a0413520 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -296,7 +296,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderInvibes: invibes.Builder, openrtb_ext.BidderIQZone: iqzone.Builder, openrtb_ext.BidderIx: ix.Builder, - openrtb_ext.BidderJANet: adtelligent.Builder, openrtb_ext.BidderJixie: jixie.Builder, openrtb_ext.BidderKargo: kargo.Builder, openrtb_ext.BidderKayzen: kayzen.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 2258cad4214..bec6d3c1e1b 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -121,7 +121,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderInvibes, BidderIQZone, BidderIx, - BidderJANet, BidderJixie, BidderKargo, BidderKayzen, @@ -412,7 +411,6 @@ const ( BidderInvibes BidderName = "invibes" BidderIQZone BidderName = "iqzone" BidderIx BidderName = "ix" - BidderJANet BidderName = "janet" BidderJixie BidderName = "jixie" BidderKargo BidderName = "kargo" BidderKayzen BidderName = "kayzen" diff --git a/static/bidder-info/janet.yaml b/static/bidder-info/janet.yaml index 0a3abb53af4..97f44a2bd56 100644 --- a/static/bidder-info/janet.yaml +++ b/static/bidder-info/janet.yaml @@ -1,15 +1,7 @@ +aliasOf: adtelligent endpoint: "http://ghb.bidder.jmgads.com/pbs/ortb" maintainer: email: "info@thejmg.com" -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video userSync: # JANet supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-params/janet.json b/static/bidder-params/janet.json deleted file mode 100644 index 38c2c2eb044..00000000000 --- a/static/bidder-params/janet.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "JANet Adapter Params", - "description": "A schema which validates params accepted by the JANet adapter", - - "type": "object", - "properties": { - "placementId": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - }, - "siteId": { - "type": "integer", - "description": "An ID which identifies the site selling the impression" - }, - "aid": { - "type": "integer", - "description": "An ID which identifies the channel" - }, - "bidFloor": { - "type": "number", - "description": "BidFloor, US Dollars" - } - }, - "required": ["aid"] -} From 83c7946ab769d9886b0967e1864c7d9535c1552a Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Wed, 18 Oct 2023 18:24:08 +0530 Subject: [PATCH 061/138] migrate janet alias to use new pattern (#3190) --- exchange/adapter_builders.go | 1 - openrtb_ext/bidders.go | 2 -- static/bidder-info/pgam.yaml | 10 +--------- static/bidder-params/pgam.json | 26 -------------------------- 4 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 static/bidder-params/pgam.json diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 573a0413520..8e97baf655f 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -330,7 +330,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderOutbrain: outbrain.Builder, openrtb_ext.BidderOwnAdx: ownadx.Builder, openrtb_ext.BidderPangle: pangle.Builder, - openrtb_ext.BidderPGAM: adtelligent.Builder, openrtb_ext.BidderPGAMSsp: pgamssp.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index bec6d3c1e1b..24f24c16278 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -155,7 +155,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderOutbrain, BidderOwnAdx, BidderPangle, - BidderPGAM, BidderPGAMSsp, BidderPubmatic, BidderPubnative, @@ -445,7 +444,6 @@ const ( BidderOutbrain BidderName = "outbrain" BidderOwnAdx BidderName = "ownadx" BidderPangle BidderName = "pangle" - BidderPGAM BidderName = "pgam" BidderPGAMSsp BidderName = "pgamssp" BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" diff --git a/static/bidder-info/pgam.yaml b/static/bidder-info/pgam.yaml index 0c9b2f008b6..e0bf4388d4b 100644 --- a/static/bidder-info/pgam.yaml +++ b/static/bidder-info/pgam.yaml @@ -1,15 +1,7 @@ +aliasOf: adtelligent endpoint: "http://ghb.pgamssp.com/pbs/ortb" maintainer: email: "ppatel@pgammedia.com" -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video userSync: # PGAM ssp supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-params/pgam.json b/static/bidder-params/pgam.json deleted file mode 100644 index c0a016a7d24..00000000000 --- a/static/bidder-params/pgam.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "PGAM Adapter Params", - "description": "A schema which validates params accepted by the PGAM ssp adapter", - - "type": "object", - "properties": { - "placementId": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - }, - "siteId": { - "type": "integer", - "description": "An ID which identifies the site selling the impression" - }, - "aid": { - "type": "integer", - "description": "An ID which identifies the channel" - }, - "bidFloor": { - "type": "number", - "description": "BidFloor, US Dollars" - } - }, - "required": ["aid"] -} From 32ddacacbcceec660d274f84b395840327af1247 Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Wed, 18 Oct 2023 18:38:40 +0530 Subject: [PATCH 062/138] migrate streamkey alias to use new pattern (#3191) --- exchange/adapter_builders.go | 1 - openrtb_ext/bidders.go | 2 -- static/bidder-info/streamkey.yaml | 1 + static/bidder-params/streamkey.json | 26 -------------------------- 4 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 static/bidder-params/streamkey.json diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 8e97baf655f..f28973ef8d9 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -357,7 +357,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderSonobi: sonobi.Builder, openrtb_ext.BidderSovrn: sovrn.Builder, openrtb_ext.BidderSspBC: sspBC.Builder, - openrtb_ext.BidderStreamkey: adtelligent.Builder, openrtb_ext.BidderSuntContent: suntContent.Builder, openrtb_ext.BidderStroeerCore: stroeerCore.Builder, openrtb_ext.BidderSynacormedia: imds.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 24f24c16278..bde67130350 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -182,7 +182,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderSonobi, BidderSovrn, BidderSspBC, - BidderStreamkey, BidderStroeerCore, BidderSuntContent, BidderSynacormedia, @@ -471,7 +470,6 @@ const ( BidderSonobi BidderName = "sonobi" BidderSovrn BidderName = "sovrn" BidderSspBC BidderName = "sspBC" - BidderStreamkey BidderName = "streamkey" BidderStroeerCore BidderName = "stroeerCore" BidderSuntContent BidderName = "suntContent" BidderSynacormedia BidderName = "synacormedia" diff --git a/static/bidder-info/streamkey.yaml b/static/bidder-info/streamkey.yaml index 9e5d05abaec..20510ceac69 100644 --- a/static/bidder-info/streamkey.yaml +++ b/static/bidder-info/streamkey.yaml @@ -1,3 +1,4 @@ +aliasOf: adtelligent endpoint: "http://ghb.hb.streamkey.net/pbs/ortb" maintainer: email: "contact@streamkey.tv" diff --git a/static/bidder-params/streamkey.json b/static/bidder-params/streamkey.json deleted file mode 100644 index ec8e5b1b643..00000000000 --- a/static/bidder-params/streamkey.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Streamkey Adapter Params", - "description": "A schema which validates params accepted by the Streamkey adapter", - - "type": "object", - "properties": { - "placementId": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - }, - "siteId": { - "type": "integer", - "description": "An ID which identifies the site selling the impression" - }, - "aid": { - "type": "integer", - "description": "An ID which identifies the channel" - }, - "bidFloor": { - "type": "number", - "description": "BidFloor, US Dollars" - } - }, - "required": ["aid"] -} From 9bfe196d3e868512dff813cd7a60721ac1b66f0e Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Wed, 18 Oct 2023 18:39:08 +0530 Subject: [PATCH 063/138] migrate viewdeos alias to use new pattern (#3192) --- exchange/adapter_builders.go | 1 - openrtb_ext/bidders.go | 2 -- static/bidder-info/viewdeos.yaml | 10 +--------- static/bidder-params/viewdeos.json | 26 -------------------------- 4 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 static/bidder-params/viewdeos.json diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index f28973ef8d9..a8b1b0ba361 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -377,7 +377,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderVideoByte: videobyte.Builder, openrtb_ext.BidderVideoHeroes: videoheroes.Builder, openrtb_ext.BidderVidoomy: vidoomy.Builder, - openrtb_ext.BidderViewdeos: adtelligent.Builder, openrtb_ext.BidderVisibleMeasures: visiblemeasures.Builder, openrtb_ext.BidderVisx: visx.Builder, openrtb_ext.BidderVox: vox.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index bde67130350..02e01c5336e 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -202,7 +202,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderVideoByte, BidderVideoHeroes, BidderVidoomy, - BidderViewdeos, BidderVisibleMeasures, BidderVisx, BidderVox, @@ -490,7 +489,6 @@ const ( BidderVideoByte BidderName = "videobyte" BidderVideoHeroes BidderName = "videoheroes" BidderVidoomy BidderName = "vidoomy" - BidderViewdeos BidderName = "viewdeos" BidderVisibleMeasures BidderName = "visiblemeasures" BidderVisx BidderName = "visx" BidderVox BidderName = "vox" diff --git a/static/bidder-info/viewdeos.yaml b/static/bidder-info/viewdeos.yaml index c5e1e8ef02c..8d14cb961d8 100644 --- a/static/bidder-info/viewdeos.yaml +++ b/static/bidder-info/viewdeos.yaml @@ -1,16 +1,8 @@ +aliasOf: adtelligent endpoint: "http://ghb.sync.viewdeos.com/pbs/ortb" maintainer: email: "contact@viewdeos.com" gvlVendorID: 924 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video userSync: # viewdeos supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-params/viewdeos.json b/static/bidder-params/viewdeos.json deleted file mode 100644 index 3e309e4b77a..00000000000 --- a/static/bidder-params/viewdeos.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Viewdeos Adapter Params", - "description": "A schema which validates params accepted by the Viewdeos adapter", - - "type": "object", - "properties": { - "placementId": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - }, - "siteId": { - "type": "integer", - "description": "An ID which identifies the site selling the impression" - }, - "aid": { - "type": "integer", - "description": "An ID which identifies the channel" - }, - "bidFloor": { - "type": "number", - "description": "BidFloor, US Dollars" - } - }, - "required": ["aid"] -} From 91881fe01ccac2eb44e517164e368b2e55e38116 Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Wed, 18 Oct 2023 19:18:59 +0530 Subject: [PATCH 064/138] migrate limelight aliases to follow new pattern (#3183) co-authored by @onkarvhanumante --- exchange/adapter_builders.go | 6 ------ openrtb_ext/bidders.go | 12 ------------ static/bidder-info/adsyield.yaml | 16 +--------------- static/bidder-info/appstock.yaml | 16 +--------------- static/bidder-info/evtech.yaml | 16 +--------------- static/bidder-info/greedygame.yaml | 16 +--------------- static/bidder-info/iionads.yaml | 16 +--------------- static/bidder-info/xtrmqb.yaml | 16 +--------------- static/bidder-params/adsyield.json | 23 ----------------------- static/bidder-params/appstock.json | 23 ----------------------- static/bidder-params/evtech.json | 23 ----------------------- static/bidder-params/greedygame.json | 23 ----------------------- static/bidder-params/iionads.json | 23 ----------------------- static/bidder-params/xtrmqb.json | 23 ----------------------- 14 files changed, 6 insertions(+), 246 deletions(-) delete mode 100644 static/bidder-params/adsyield.json delete mode 100644 static/bidder-params/appstock.json delete mode 100644 static/bidder-params/evtech.json delete mode 100644 static/bidder-params/greedygame.json delete mode 100644 static/bidder-params/iionads.json delete mode 100644 static/bidder-params/xtrmqb.json diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index a8b1b0ba361..a65e051ebf3 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -213,7 +213,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAdquery: adquery.Builder, openrtb_ext.BidderAdrino: adrino.Builder, openrtb_ext.BidderAdsinteractive: adsinteractive.Builder, - openrtb_ext.BidderAdsyield: limelightDigital.Builder, openrtb_ext.BidderAdtarget: adtarget.Builder, openrtb_ext.BidderAdtrgtme: adtrgtme.Builder, openrtb_ext.BidderAdtelligent: adtelligent.Builder, @@ -227,7 +226,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAMX: amx.Builder, openrtb_ext.BidderApacdex: apacdex.Builder, openrtb_ext.BidderAppnexus: appnexus.Builder, - openrtb_ext.BidderAppstock: limelightDigital.Builder, openrtb_ext.BidderAppush: appush.Builder, openrtb_ext.BidderAudienceNetwork: audienceNetwork.Builder, openrtb_ext.BidderAutomatad: automatad.Builder, @@ -273,7 +271,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderEpom: epom.Builder, openrtb_ext.BidderEpsilon: conversant.Builder, openrtb_ext.BidderEVolution: evolution.Builder, - openrtb_ext.BidderEvtech: limelightDigital.Builder, openrtb_ext.BidderFlipp: flipp.Builder, openrtb_ext.BidderFreewheelSSP: freewheelssp.Builder, openrtb_ext.BidderFreewheelSSPOld: freewheelssp.Builder, @@ -282,11 +279,9 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderGamoshi: gamoshi.Builder, openrtb_ext.BidderGlobalsun: globalsun.Builder, openrtb_ext.BidderGothamads: gothamads.Builder, - openrtb_ext.BidderGreedygame: limelightDigital.Builder, openrtb_ext.BidderGrid: grid.Builder, openrtb_ext.BidderGumGum: gumgum.Builder, openrtb_ext.BidderHuaweiAds: huaweiads.Builder, - openrtb_ext.BidderIionads: limelightDigital.Builder, openrtb_ext.BidderImds: imds.Builder, openrtb_ext.BidderImpactify: impactify.Builder, openrtb_ext.BidderImprovedigital: improvedigital.Builder, @@ -382,7 +377,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderVox: vox.Builder, openrtb_ext.BidderVrtcal: vrtcal.Builder, openrtb_ext.BidderXeworks: xeworks.Builder, - openrtb_ext.BidderXtrmqb: limelightDigital.Builder, openrtb_ext.BidderYahooAds: yahooAds.Builder, openrtb_ext.BidderYahooAdvertising: yahooAds.Builder, openrtb_ext.BidderYahooSSP: yahooAds.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 02e01c5336e..b14eeb49b35 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -38,7 +38,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderAdquery, BidderAdrino, BidderAdsinteractive, - BidderAdsyield, BidderAdtarget, BidderAdtrgtme, BidderAdtelligent, @@ -52,7 +51,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderAMX, BidderApacdex, BidderAppnexus, - BidderAppstock, BidderAppush, BidderAudienceNetwork, BidderAutomatad, @@ -98,7 +96,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderEpom, BidderEpsilon, BidderEVolution, - BidderEvtech, BidderFlipp, BidderFreewheelSSP, BidderFreewheelSSPOld, @@ -107,11 +104,9 @@ var coreBidderNames []BidderName = []BidderName{ BidderGamoshi, BidderGlobalsun, BidderGothamads, - BidderGreedygame, BidderGrid, BidderGumGum, BidderHuaweiAds, - BidderIionads, BidderImds, BidderImpactify, BidderImprovedigital, @@ -207,7 +202,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderVox, BidderVrtcal, BidderXeworks, - BidderXtrmqb, BidderYahooAds, BidderYahooAdvertising, BidderYahooSSP, @@ -325,7 +319,6 @@ const ( BidderAdquery BidderName = "adquery" BidderAdrino BidderName = "adrino" BidderAdsinteractive BidderName = "adsinteractive" - BidderAdsyield BidderName = "adsyield" BidderAdtarget BidderName = "adtarget" BidderAdtrgtme BidderName = "adtrgtme" BidderAdtelligent BidderName = "adtelligent" @@ -339,7 +332,6 @@ const ( BidderAMX BidderName = "amx" BidderApacdex BidderName = "apacdex" BidderAppnexus BidderName = "appnexus" - BidderAppstock BidderName = "appstock" BidderAppush BidderName = "appush" BidderAudienceNetwork BidderName = "audienceNetwork" BidderAutomatad BidderName = "automatad" @@ -385,7 +377,6 @@ const ( BidderEpsilon BidderName = "epsilon" BidderEpom BidderName = "epom" BidderEVolution BidderName = "e_volution" - BidderEvtech BidderName = "evtech" BidderFlipp BidderName = "flipp" BidderFreewheelSSP BidderName = "freewheelssp" BidderFreewheelSSPOld BidderName = "freewheel-ssp" @@ -394,11 +385,9 @@ const ( BidderGamoshi BidderName = "gamoshi" BidderGlobalsun BidderName = "globalsun" BidderGothamads BidderName = "gothamads" - BidderGreedygame BidderName = "greedygame" BidderGrid BidderName = "grid" BidderGumGum BidderName = "gumgum" BidderHuaweiAds BidderName = "huaweiads" - BidderIionads BidderName = "iionads" BidderImds BidderName = "imds" BidderImpactify BidderName = "impactify" BidderImprovedigital BidderName = "improvedigital" @@ -494,7 +483,6 @@ const ( BidderVox BidderName = "vox" BidderVrtcal BidderName = "vrtcal" BidderXeworks BidderName = "xeworks" - BidderXtrmqb BidderName = "xtrmqb" BidderYahooAds BidderName = "yahooAds" BidderYahooAdvertising BidderName = "yahooAdvertising" BidderYahooSSP BidderName = "yahoossp" diff --git a/static/bidder-info/adsyield.yaml b/static/bidder-info/adsyield.yaml index dadd9cc3d13..2be1395b851 100644 --- a/static/bidder-info/adsyield.yaml +++ b/static/bidder-info/adsyield.yaml @@ -1,16 +1,2 @@ endpoint: "http://ads-pbs.open-adsyield.com/openrtb/{{.PublisherID}}?host={{.Host}}" -maintainer: - email: "engineering@project-limelight.com" -capabilities: - app: - mediaTypes: - - banner - - video - - audio - - native - site: - mediaTypes: - - banner - - video - - audio - - native +aliasOf: "limelightDigital" diff --git a/static/bidder-info/appstock.yaml b/static/bidder-info/appstock.yaml index 28f420e9e88..73b54aa7144 100644 --- a/static/bidder-info/appstock.yaml +++ b/static/bidder-info/appstock.yaml @@ -1,16 +1,2 @@ endpoint: "http://ads-pbs.pre.vr-tb.com/openrtb/{{.PublisherID}}?host={{.Host}}" -maintainer: - email: "engineering@project-limelight.com" -capabilities: - app: - mediaTypes: - - banner - - video - - audio - - native - site: - mediaTypes: - - banner - - video - - audio - - native +aliasOf: "limelightDigital" diff --git a/static/bidder-info/evtech.yaml b/static/bidder-info/evtech.yaml index 4277a5c46c8..59134e04523 100644 --- a/static/bidder-info/evtech.yaml +++ b/static/bidder-info/evtech.yaml @@ -1,19 +1,5 @@ endpoint: "http://ads-pbs.direct.e-volution.ai/openrtb/{{.PublisherID}}?host={{.Host}}" -maintainer: - email: "engineering@project-limelight.com" -capabilities: - app: - mediaTypes: - - banner - - video - - audio - - native - site: - mediaTypes: - - banner - - video - - audio - - native +aliasOf: "limelightDigital" userSync: iframe: url: https://tracker.direct.e-volution.ai/sync.html?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}} diff --git a/static/bidder-info/greedygame.yaml b/static/bidder-info/greedygame.yaml index 5e73e1fe950..330a7debd9d 100644 --- a/static/bidder-info/greedygame.yaml +++ b/static/bidder-info/greedygame.yaml @@ -1,16 +1,2 @@ endpoint: "http://ads-pbs.rtb-greedygame.com/openrtb/{{.PublisherID}}?host={{.Host}}" -maintainer: - email: "engineering@project-limelight.com" -capabilities: - app: - mediaTypes: - - banner - - video - - audio - - native - site: - mediaTypes: - - banner - - video - - audio - - native +aliasOf: "limelightDigital" diff --git a/static/bidder-info/iionads.yaml b/static/bidder-info/iionads.yaml index da2f494bb30..1dc154a358d 100644 --- a/static/bidder-info/iionads.yaml +++ b/static/bidder-info/iionads.yaml @@ -1,16 +1,2 @@ endpoint: "http://ads-pbs.iionads.com/openrtb/{{.PublisherID}}?host={{.Host}}" -maintainer: - email: "engineering@project-limelight.com" -capabilities: - app: - mediaTypes: - - banner - - video - - audio - - native - site: - mediaTypes: - - banner - - video - - audio - - native +aliasOf: "limelightDigital" \ No newline at end of file diff --git a/static/bidder-info/xtrmqb.yaml b/static/bidder-info/xtrmqb.yaml index c1f0da3fc1f..b7a068c3889 100644 --- a/static/bidder-info/xtrmqb.yaml +++ b/static/bidder-info/xtrmqb.yaml @@ -1,16 +1,2 @@ endpoint: "http://ads-pbs.ortb.tech/openrtb/{{.PublisherID}}?host={{.Host}}" -maintainer: - email: "engineering@project-limelight.com" -capabilities: - app: - mediaTypes: - - banner - - video - - audio - - native - site: - mediaTypes: - - banner - - video - - audio - - native +aliasOf: "limelightDigital" diff --git a/static/bidder-params/adsyield.json b/static/bidder-params/adsyield.json deleted file mode 100644 index c7c5308890f..00000000000 --- a/static/bidder-params/adsyield.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Adsyield Adapter Params", - "description": "A schema which validates params accepted by the Adsyield adapter", - "type": "object", - - "properties": { - "host": { - "type": "string", - "description": "Ad network's RTB host", - "format": "hostname", - "pattern": "^.+\\..+$" - }, - "publisherId": { - "type": ["integer", "string"], - "description": "Publisher ID", - "minimum": 1, - "pattern": "^[1-9][0-9]*$" - } - }, - - "required": ["host", "publisherId"] -} diff --git a/static/bidder-params/appstock.json b/static/bidder-params/appstock.json deleted file mode 100644 index eb2251e4e47..00000000000 --- a/static/bidder-params/appstock.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Appstock Adapter Params", - "description": "A schema which validates params accepted by the Appstock adapter", - "type": "object", - - "properties": { - "host": { - "type": "string", - "description": "Ad network's RTB host", - "format": "hostname", - "pattern": "^.+\\..+$" - }, - "publisherId": { - "type": ["integer", "string"], - "description": "Publisher ID", - "minimum": 1, - "pattern": "^[1-9][0-9]*$" - } - }, - - "required": ["host", "publisherId"] -} diff --git a/static/bidder-params/evtech.json b/static/bidder-params/evtech.json deleted file mode 100644 index 5e78b7de171..00000000000 --- a/static/bidder-params/evtech.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Evolution Technologies Adapter Params", - "description": "A schema which validates params accepted by the Evolution Technologies adapter", - "type": "object", - - "properties": { - "host": { - "type": "string", - "description": "Ad network's RTB host", - "format": "hostname", - "pattern": "^.+\\..+$" - }, - "publisherId": { - "type": ["integer", "string"], - "description": "Publisher ID", - "minimum": 1, - "pattern": "^[1-9][0-9]*$" - } - }, - - "required": ["host", "publisherId"] -} diff --git a/static/bidder-params/greedygame.json b/static/bidder-params/greedygame.json deleted file mode 100644 index 2057c147d44..00000000000 --- a/static/bidder-params/greedygame.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "GreedyGame Adapter Params", - "description": "A schema which validates params accepted by the GreedyGame adapter", - "type": "object", - - "properties": { - "host": { - "type": "string", - "description": "Ad network's RTB host", - "format": "hostname", - "pattern": "^.+\\..+$" - }, - "publisherId": { - "type": ["integer", "string"], - "description": "Publisher ID", - "minimum": 1, - "pattern": "^[1-9][0-9]*$" - } - }, - - "required": ["host", "publisherId"] -} diff --git a/static/bidder-params/iionads.json b/static/bidder-params/iionads.json deleted file mode 100644 index b9196445acc..00000000000 --- a/static/bidder-params/iionads.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Iion Adapter Params", - "description": "A schema which validates params accepted by the Iion adapter", - "type": "object", - - "properties": { - "host": { - "type": "string", - "description": "Ad network's RTB host", - "format": "hostname", - "pattern": "^.+\\..+$" - }, - "publisherId": { - "type": ["integer", "string"], - "description": "Publisher ID", - "minimum": 1, - "pattern": "^[1-9][0-9]*$" - } - }, - - "required": ["host", "publisherId"] -} diff --git a/static/bidder-params/xtrmqb.json b/static/bidder-params/xtrmqb.json deleted file mode 100644 index 59e711e9ad3..00000000000 --- a/static/bidder-params/xtrmqb.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "XTRM QB Adapter Params", - "description": "A schema which validates params accepted by the XTRM QB adapter", - "type": "object", - - "properties": { - "host": { - "type": "string", - "description": "Ad network's RTB host", - "format": "hostname", - "pattern": "^.+\\..+$" - }, - "publisherId": { - "type": ["integer", "string"], - "description": "Publisher ID", - "minimum": 1, - "pattern": "^[1-9][0-9]*$" - } - }, - - "required": ["host", "publisherId"] -} From ac7a0d40d11f12e950270857f2a2aca8178ae1fc Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Wed, 18 Oct 2023 19:19:58 +0530 Subject: [PATCH 065/138] migrate apacdex aliases to use new pattern (#3184) co-authored: @onkarvhanumante --- exchange/adapter_builders.go | 2 -- openrtb_ext/bidders.go | 4 --- static/bidder-info/quantumdex.yaml | 21 +-------------- static/bidder-info/valueimpression.yaml | 21 +-------------- static/bidder-params/quantumdex.json | 32 ----------------------- static/bidder-params/valueimpression.json | 32 ----------------------- 6 files changed, 2 insertions(+), 110 deletions(-) delete mode 100644 static/bidder-params/quantumdex.json delete mode 100644 static/bidder-params/valueimpression.json diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index a65e051ebf3..29e16bb2904 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -330,7 +330,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderPubnative: pubnative.Builder, openrtb_ext.BidderPulsepoint: pulsepoint.Builder, openrtb_ext.BidderPWBid: pwbid.Builder, - openrtb_ext.BidderQuantumdex: apacdex.Builder, openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRichaudience: richaudience.Builder, openrtb_ext.BidderRise: rise.Builder, @@ -368,7 +367,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderUndertone: undertone.Builder, openrtb_ext.BidderUnicorn: unicorn.Builder, openrtb_ext.BidderUnruly: unruly.Builder, - openrtb_ext.BidderValueImpression: apacdex.Builder, openrtb_ext.BidderVideoByte: videobyte.Builder, openrtb_ext.BidderVideoHeroes: videoheroes.Builder, openrtb_ext.BidderVidoomy: vidoomy.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index b14eeb49b35..709c3aa3cdd 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -155,7 +155,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderPubnative, BidderPulsepoint, BidderPWBid, - BidderQuantumdex, BidderRevcontent, BidderRichaudience, BidderRise, @@ -193,7 +192,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderUndertone, BidderUnicorn, BidderUnruly, - BidderValueImpression, BidderVideoByte, BidderVideoHeroes, BidderVidoomy, @@ -436,7 +434,6 @@ const ( BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" BidderPWBid BidderName = "pwbid" - BidderQuantumdex BidderName = "quantumdex" BidderRevcontent BidderName = "revcontent" BidderRichaudience BidderName = "richaudience" BidderRise BidderName = "rise" @@ -474,7 +471,6 @@ const ( BidderUndertone BidderName = "undertone" BidderUnicorn BidderName = "unicorn" BidderUnruly BidderName = "unruly" - BidderValueImpression BidderName = "valueimpression" BidderVideoByte BidderName = "videobyte" BidderVideoHeroes BidderName = "videoheroes" BidderVidoomy BidderName = "vidoomy" diff --git a/static/bidder-info/quantumdex.yaml b/static/bidder-info/quantumdex.yaml index 590737ac28b..32043ad62fb 100644 --- a/static/bidder-info/quantumdex.yaml +++ b/static/bidder-info/quantumdex.yaml @@ -1,20 +1 @@ -endpoint: "http://useast.quantumdex.io/auction/pbs" -maintainer: - email: "support@apacdex.com" -modifyingVastXmlAllowed: false -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video -userSync: - iframe: - url: https://sync.quantumdex.io/usersync/pbs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}} - userMacro: "[UID]" - redirect: - url: "https://sync.quantumdex.io/getuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" - userMacro: "[UID]" +aliasOf: "apacdex" \ No newline at end of file diff --git a/static/bidder-info/valueimpression.yaml b/static/bidder-info/valueimpression.yaml index 9e6e583967c..32043ad62fb 100644 --- a/static/bidder-info/valueimpression.yaml +++ b/static/bidder-info/valueimpression.yaml @@ -1,20 +1 @@ -endpoint: "http://useast.quantumdex.io/auction/pbs" -maintainer: - email: "support@apacdex.com" -modifyingVastXmlAllowed: false -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video -userSync: - iframe: - url: https://sync.quantumdex.io/usersync/pbs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}} - userMacro: "[UID]" - redirect: - url: "https://sync.quantumdex.io/getuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" - userMacro: "[UID]" \ No newline at end of file +aliasOf: "apacdex" \ No newline at end of file diff --git a/static/bidder-params/quantumdex.json b/static/bidder-params/quantumdex.json deleted file mode 100644 index 3ef9abe34c6..00000000000 --- a/static/bidder-params/quantumdex.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Quantumdex Adapter Params", - "description": "A schema which validates params accepted by the Quantumdex adapter", - "type": "object", - "properties": { - "placementId": { - "type": "string", - "description": "Placement ID provided by Quantumdex" - }, - "siteId": { - "type": "string", - "description": "Publisher site ID from Quantumdex" - }, - "floorPrice": { - "type": "number", - "description": "CPM bidfloor in USD" - } - }, - "oneOf": [ - { - "required": [ - "placementId" - ] - }, - { - "required": [ - "siteId" - ] - } - ] -} \ No newline at end of file diff --git a/static/bidder-params/valueimpression.json b/static/bidder-params/valueimpression.json deleted file mode 100644 index 793e940eb11..00000000000 --- a/static/bidder-params/valueimpression.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Valueimpression Adapter Params", - "description": "A schema which validates params accepted by the Valueimpression adapter", - "type": "object", - "properties": { - "placementId": { - "type": "string", - "description": "Placement ID provided by Valueimpression" - }, - "siteId": { - "type": "string", - "description": "Publisher site ID from Valueimpression" - }, - "floorPrice": { - "type": "number", - "description": "CPM bidfloor in USD" - } - }, - "oneOf": [ - { - "required": [ - "placementId" - ] - }, - { - "required": [ - "siteId" - ] - } - ] -} \ No newline at end of file From bc77035476d38200946f3f1b0ae9a6a76207eac3 Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Thu, 19 Oct 2023 04:55:44 +0530 Subject: [PATCH 066/138] Update release workflow to support major version release (#3235) --- .github/workflows/release.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fd790699a14..f1d2b10c41c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,11 +6,12 @@ on: releaseType: type: choice options: + - major - minor - patch default: minor required: true - description: 'minor: v0.X.0, patch: v0.0.X' + description: 'major: vX.0.0, minor: v0.X.0, patch: v0.0.X' debug: type: boolean default: true @@ -78,11 +79,18 @@ jobs: nextTag='' releaseType=${{ inputs.releaseType }} - if [ $releaseType == "minor" ]; then - # increment minor version and reset patch version - nextTag=$(echo "${currentTag}" | awk -F. '{OFS="."; $2+=1; $3=0; print $0}') + if [ $releaseType == "major" ]; then + # PBS-GO skipped the v1.0.0 major release - https://github.com/prebid/prebid-server/issues/3068 + # If the current tag is v0.x.x, the script sets the next release tag to v2.0.0 + # Otherwise, the script increments the major version by 1 and sets the minor and patch versions to zero + # For example, v2.x.x will be incremented to v3.0.0 + major=$(echo "${currentTag}" | awk -F. '{gsub(/^v/, "", $1); if($1 == 0) $1=2; else $1+=1; print $1}') + nextTag="v${major}.0.0" + elif [ $releaseType == "minor" ]; then + # Increment minor version and reset patch version + nextTag=$(echo "${currentTag}" | awk -F. '{OFS="."; $2+=1; $3=0; print $0}') else - # increment patch version + # Increment patch version nextTag=$(echo "${currentTag}" | awk -F. '{OFS="."; $3+=1; print $0}') fi From 8131a14f69dbb585a3f0b699e794634589b823ee Mon Sep 17 00:00:00 2001 From: IQzoneIT Date: Thu, 19 Oct 2023 02:08:33 -0400 Subject: [PATCH 067/138] New Adapter: IQX (#3164) Co-authored-by: Dmytro Shyrokov --- adapters/iqx/iqx.go | 164 ++++++++++ adapters/iqx/iqxtest_test.go | 27 ++ .../iqx/iqzonextest/exemplary/banner.json | 283 ++++++++++++++++++ .../iqx/iqzonextest/exemplary/native.json | 164 ++++++++++ adapters/iqx/iqzonextest/exemplary/video.json | 204 +++++++++++++ .../supplemental/bad-response.json | 106 +++++++ .../supplemental/empty-mediatype.json | 190 ++++++++++++ .../supplemental/empty-seatbid-0-bid.json | 111 +++++++ .../supplemental/empty-seatbid.json | 111 +++++++ .../invalid-ext-bidder-object.json | 49 +++ .../supplemental/invalid-ext-object.json | 47 +++ .../supplemental/invalid-mediatype.json | 187 ++++++++++++ .../iqzonextest/supplemental/status-204.json | 100 +++++++ .../iqzonextest/supplemental/status-400.json | 106 +++++++ .../iqzonextest/supplemental/status-503.json | 105 +++++++ .../supplemental/unexpected-status.json | 106 +++++++ adapters/iqx/params_test.go | 53 ++++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_iqx.go | 6 + static/bidder-info/iqx.yaml | 19 ++ static/bidder-params/iqx.json | 22 ++ 22 files changed, 2164 insertions(+) create mode 100644 adapters/iqx/iqx.go create mode 100644 adapters/iqx/iqxtest_test.go create mode 100644 adapters/iqx/iqzonextest/exemplary/banner.json create mode 100644 adapters/iqx/iqzonextest/exemplary/native.json create mode 100644 adapters/iqx/iqzonextest/exemplary/video.json create mode 100644 adapters/iqx/iqzonextest/supplemental/bad-response.json create mode 100644 adapters/iqx/iqzonextest/supplemental/empty-mediatype.json create mode 100644 adapters/iqx/iqzonextest/supplemental/empty-seatbid-0-bid.json create mode 100644 adapters/iqx/iqzonextest/supplemental/empty-seatbid.json create mode 100644 adapters/iqx/iqzonextest/supplemental/invalid-ext-bidder-object.json create mode 100644 adapters/iqx/iqzonextest/supplemental/invalid-ext-object.json create mode 100644 adapters/iqx/iqzonextest/supplemental/invalid-mediatype.json create mode 100644 adapters/iqx/iqzonextest/supplemental/status-204.json create mode 100644 adapters/iqx/iqzonextest/supplemental/status-400.json create mode 100644 adapters/iqx/iqzonextest/supplemental/status-503.json create mode 100644 adapters/iqx/iqzonextest/supplemental/unexpected-status.json create mode 100644 adapters/iqx/params_test.go create mode 100644 openrtb_ext/imp_iqx.go create mode 100644 static/bidder-info/iqx.yaml create mode 100644 static/bidder-params/iqx.json diff --git a/adapters/iqx/iqx.go b/adapters/iqx/iqx.go new file mode 100644 index 00000000000..543d988bce5 --- /dev/null +++ b/adapters/iqx/iqx.go @@ -0,0 +1,164 @@ +package iqx + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type bidType struct { + Type string `json:"type"` +} + +type bidExt struct { + Prebid bidType `json:"prebid"` +} + +type adapter struct { + endpoint *template.Template +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + tmpl, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint URL template: %v", err) + } + + bidder := &adapter{ + endpoint: tmpl, + } + + return bidder, nil +} + +func (a *adapter) buildEndpointFromRequest(imp *openrtb2.Imp) (string, error) { + var impExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &impExt); err != nil { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to deserialize bidder impression extension: %v", err), + } + } + + var iqzonexExt openrtb_ext.ExtIQX + if err := json.Unmarshal(impExt.Bidder, &iqzonexExt); err != nil { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to deserialize IQZonex extension: %v", err), + } + } + + endpointParams := macros.EndpointTemplateParams{ + Host: iqzonexExt.Env, + SourceId: iqzonexExt.Pid, + } + + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var requests []*adapters.RequestData + var errs []error + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + requestCopy := *request + for _, imp := range request.Imp { + requestCopy.Imp = []openrtb2.Imp{imp} + + endpoint, err := a.buildEndpointFromRequest(&imp) + if err != nil { + errs = append(errs, err) + continue + } + + requestJSON, err := json.Marshal(requestCopy) + if err != nil { + errs = append(errs, err) + continue + } + + request := &adapters.RequestData{ + Method: http.MethodPost, + Body: requestJSON, + Uri: endpoint, + Headers: headers, + } + + requests = append(requests, request) + } + + return requests, errs +} + +func (a *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(bidderRawResponse) { + return nil, nil + } + + if bidderRawResponse.StatusCode == http.StatusServiceUnavailable { + return nil, []error{&errortypes.BadInput{ + Message: "Bidder IQZonex is unavailable. Please contact the bidder support.", + }} + } + + if err := adapters.CheckResponseStatusCodeForErrors(bidderRawResponse); err != nil { + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(bidderRawResponse.Body, &bidResp); err != nil { + return nil, []error{err} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Array SeatBid cannot be empty", + }} + } + + return prepareBidResponse(bidResp.SeatBid) +} + +func prepareBidResponse(seats []openrtb2.SeatBid) (*adapters.BidderResponse, []error) { + errs := []error{} + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(seats)) + + for _, seatBid := range seats { + for bidId, bid := range seatBid.Bid { + bidType, err := getMediaTypeForBid(bid) + if err != nil { + errs = append(errs, err) + continue + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[bidId], + BidType: bidType, + }) + } + } + + return bidResponse, errs +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("failed to parse bid mtype for impression id \"%s\"", bid.ImpID) + } +} diff --git a/adapters/iqx/iqxtest_test.go b/adapters/iqx/iqxtest_test.go new file mode 100644 index 00000000000..18eaf5fa5ba --- /dev/null +++ b/adapters/iqx/iqxtest_test.go @@ -0,0 +1,27 @@ +package iqx + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder( + openrtb_ext.BidderIQX, + config.Adapter{ + Endpoint: "http://rtb.iqzone.com/?pid={{.SourceId}}&host={{.Host}}&pbs=1", + }, + config.Server{ + ExternalUrl: "http://hosturl.com", + GvlID: 1, + DataCenter: "2", + }, + ) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "iqzonextest", bidder) +} diff --git a/adapters/iqx/iqzonextest/exemplary/banner.json b/adapters/iqx/iqzonextest/exemplary/banner.json new file mode 100644 index 00000000000..303de661a2d --- /dev/null +++ b/adapters/iqx/iqzonextest/exemplary/banner.json @@ -0,0 +1,283 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "1", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "pos": 0 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + }, + { + "id": "2", + "secure": 1, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "pos": 4 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "1", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "pos": 0 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "1", + "price": 1.2, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test2", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid1", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "2", + "secure": 1, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "pos": 4 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "2", + "price": 2.4, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test3", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "1", + "price": 1.2, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test2", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid1", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "2", + "price": 2.4, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test3", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/iqx/iqzonextest/exemplary/native.json b/adapters/iqx/iqzonextest/exemplary/native.json new file mode 100644 index 00000000000..f458d70e127 --- /dev/null +++ b/adapters/iqx/iqzonextest/exemplary/native.json @@ -0,0 +1,164 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{}", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "mtype": 4, + "ext": { + "prebid": { + "type": "native" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{}", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "mtype": 4, + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/iqx/iqzonextest/exemplary/video.json b/adapters/iqx/iqzonextest/exemplary/video.json new file mode 100644 index 00000000000..21826ac0440 --- /dev/null +++ b/adapters/iqx/iqzonextest/exemplary/video.json @@ -0,0 +1,204 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "mtype": 2, + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "mtype": 2, + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/iqx/iqzonextest/supplemental/bad-response.json b/adapters/iqx/iqzonextest/supplemental/bad-response.json new file mode 100644 index 00000000000..3d9ff9ffcd3 --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/bad-response.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/iqx/iqzonextest/supplemental/empty-mediatype.json b/adapters/iqx/iqzonextest/supplemental/empty-mediatype.json new file mode 100644 index 00000000000..3f471c9c06e --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/empty-mediatype.json @@ -0,0 +1,190 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test1", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "mtype": 1, + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, { + "id": "id", + "impid": "2", + "price": 1.2, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test2", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "some": "value" + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "failed to parse bid mtype for impression id \"2\"", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency":"USD", + "bids":[ + { + "bid": { + "id": "id", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test1", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/iqx/iqzonextest/supplemental/empty-seatbid-0-bid.json b/adapters/iqx/iqzonextest/supplemental/empty-seatbid-0-bid.json new file mode 100644 index 00000000000..66b78f4bfbd --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/empty-seatbid-0-bid.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [], + "expectedBidResponses": [{"currency":"USD","bids":[]}] +} diff --git a/adapters/iqx/iqzonextest/supplemental/empty-seatbid.json b/adapters/iqx/iqzonextest/supplemental/empty-seatbid.json new file mode 100644 index 00000000000..4df7e16f767 --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/empty-seatbid.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Array SeatBid cannot be empty", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/iqx/iqzonextest/supplemental/invalid-ext-bidder-object.json b/adapters/iqx/iqzonextest/supplemental/invalid-ext-bidder-object.json new file mode 100644 index 00000000000..2f124a8cf3b --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/invalid-ext-bidder-object.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": [] + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "Failed to deserialize IQZonex extension: json: cannot unmarshal array into Go value of type openrtb_ext.ExtIQX", + "comparison": "literal" + } + ] +} diff --git a/adapters/iqx/iqzonextest/supplemental/invalid-ext-object.json b/adapters/iqx/iqzonextest/supplemental/invalid-ext-object.json new file mode 100644 index 00000000000..aa215eb3e34 --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/invalid-ext-object.json @@ -0,0 +1,47 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": "" + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "Failed to deserialize bidder impression extension: json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] +} diff --git a/adapters/iqx/iqzonextest/supplemental/invalid-mediatype.json b/adapters/iqx/iqzonextest/supplemental/invalid-mediatype.json new file mode 100644 index 00000000000..f2ded0f0cb4 --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/invalid-mediatype.json @@ -0,0 +1,187 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test1", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + }, { + "id": "id", + "impid": "2", + "price": 1.2, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test2", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250 + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "failed to parse bid mtype for impression id \"2\"", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency":"USD", + "bids":[ + { + "bid": { + "id": "id", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test1", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/iqx/iqzonextest/supplemental/status-204.json b/adapters/iqx/iqzonextest/supplemental/status-204.json new file mode 100644 index 00000000000..c850d34b527 --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/status-204.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/iqx/iqzonextest/supplemental/status-400.json b/adapters/iqx/iqzonextest/supplemental/status-400.json new file mode 100644 index 00000000000..7e3d17d3baf --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/status-400.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 400, + "body": "The Key has a different ad format" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/iqx/iqzonextest/supplemental/status-503.json b/adapters/iqx/iqzonextest/supplemental/status-503.json new file mode 100644 index 00000000000..1b6dd02af8c --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/status-503.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 503 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bidder IQZonex is unavailable. Please contact the bidder support.", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/iqx/iqzonextest/supplemental/unexpected-status.json b/adapters/iqx/iqzonextest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..edfdf5fdd80 --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/unexpected-status.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 403, + "body": "Access is denied" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 403. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/iqx/params_test.go b/adapters/iqx/params_test.go new file mode 100644 index 00000000000..94bfb1d0926 --- /dev/null +++ b/adapters/iqx/params_test.go @@ -0,0 +1,53 @@ +package iqx + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{"env":"iqzonex-stage", "pid":"123456"}`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderIQX, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected iqzonex params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `[]`, + `{}`, + `{"some": "param"}`, + `{"env":"iqzonex-stage"}`, + `{"pid":"1234"}`, + `{"othervalue":"Lorem ipsum"}`, + `{"env":"iqzonex-stage", pid:""}`, + `{"env":"", pid:"1234"}`, +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderIQX, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 29e16bb2904..d6b4400cd05 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -95,6 +95,7 @@ import ( "github.com/prebid/prebid-server/adapters/inmobi" "github.com/prebid/prebid-server/adapters/interactiveoffers" "github.com/prebid/prebid-server/adapters/invibes" + "github.com/prebid/prebid-server/adapters/iqx" "github.com/prebid/prebid-server/adapters/iqzone" "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/jixie" @@ -289,6 +290,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderInMobi: inmobi.Builder, openrtb_ext.BidderInteractiveoffers: interactiveoffers.Builder, openrtb_ext.BidderInvibes: invibes.Builder, + openrtb_ext.BidderIQX: iqx.Builder, openrtb_ext.BidderIQZone: iqzone.Builder, openrtb_ext.BidderIx: ix.Builder, openrtb_ext.BidderJixie: jixie.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 709c3aa3cdd..ae0f7b6c82a 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -114,6 +114,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderInMobi, BidderInteractiveoffers, BidderInvibes, + BidderIQX, BidderIQZone, BidderIx, BidderJixie, @@ -393,6 +394,7 @@ const ( BidderInMobi BidderName = "inmobi" BidderInteractiveoffers BidderName = "interactiveoffers" BidderInvibes BidderName = "invibes" + BidderIQX BidderName = "iqx" BidderIQZone BidderName = "iqzone" BidderIx BidderName = "ix" BidderJixie BidderName = "jixie" diff --git a/openrtb_ext/imp_iqx.go b/openrtb_ext/imp_iqx.go new file mode 100644 index 00000000000..0b0358b67e1 --- /dev/null +++ b/openrtb_ext/imp_iqx.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtIQX struct { + Env string `json:"env"` + Pid string `json:"pid"` +} diff --git a/static/bidder-info/iqx.yaml b/static/bidder-info/iqx.yaml new file mode 100644 index 00000000000..ab10ad96289 --- /dev/null +++ b/static/bidder-info/iqx.yaml @@ -0,0 +1,19 @@ +endpoint: "http://rtb.iqzone.com?pid={{.SourceId}}&host={{.Host}}&pbs=1" +maintainer: + email: "it@iqzone.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + # IQX supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-params/iqx.json b/static/bidder-params/iqx.json new file mode 100644 index 00000000000..447c92beeb8 --- /dev/null +++ b/static/bidder-params/iqx.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "IQX Adapter Params", + "description": "A schema which validates params accepted by the iqzonex adapter", + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "IQX environment", + "minLength": 1 + }, + "pid": { + "type": "string", + "description": "Unique placement ID", + "minLength": 1 + } + }, + "required": [ + "env", + "pid" + ] +} From e8d93ccbc4a6c391176bbd6b1696d920b1aca840 Mon Sep 17 00:00:00 2001 From: Ahmet Faruk Karakus <65093478+ahmetfaruk59@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:23:38 +0300 Subject: [PATCH 068/138] HuaweiAds: Fixing the fill rate issue (#3219) --- adapters/huaweiads/huaweiads.go | 12 +++++++++++- .../huaweiadstest/exemplary/nativeIncludeVideo.json | 2 ++ .../huaweiadstest/exemplary/nativeSingleImage.json | 2 ++ .../huaweiadstest/exemplary/nativeThreeImage.json | 2 ++ .../exemplary/nativeThreeImageIncludeIcon.json | 2 ++ .../supplemental/bad_response_not_native.json | 4 +++- 6 files changed, 22 insertions(+), 2 deletions(-) diff --git a/adapters/huaweiads/huaweiads.go b/adapters/huaweiads/huaweiads.go index 2b7c3c2548d..d156c2f750f 100644 --- a/adapters/huaweiads/huaweiads.go +++ b/adapters/huaweiads/huaweiads.go @@ -536,7 +536,8 @@ func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error { // only compute the main image number, type = native1.ImageAssetTypeMain var numMainImage = 0 var numVideo = 0 - + var width int64 + var height int64 for _, asset := range nativePayload.Assets { // Only one of the {title,img,video,data} objects should be present in each object. if asset.Video != nil { @@ -547,10 +548,19 @@ func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error { if asset.Img != nil { if asset.Img.Type == native1.ImageAssetTypeMain { numMainImage++ + if asset.Img.H != 0 && asset.Img.W != 0 { + width = asset.Img.W + height = asset.Img.H + } else if asset.Img.WMin != 0 && asset.Img.HMin != 0 { + width = asset.Img.WMin + height = asset.Img.HMin + } } continue } } + adslot30.W = width + adslot30.H = height var detailedCreativeTypeList = make([]string, 0, 2) if numVideo >= 1 { diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json index 24aad066b3d..673d6a39a99 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json @@ -95,6 +95,8 @@ "detailedCreativeTypeList": [ "903" ], + "h": 200, + "w": 200, "test": 1 } ], diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json index 8e86675b4a6..d296010cc77 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json @@ -93,6 +93,8 @@ "detailedCreativeTypeList": [ "901" ], + "h": 200, + "w": 200, "test": 1 } ], diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json index c29b2352d59..16313a5588a 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json @@ -92,6 +92,8 @@ "detailedCreativeTypeList": [ "904" ], + "h": 200, + "w": 200, "test": 1 } ], diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json index 5d3af50621a..6122af732c2 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json @@ -93,6 +93,8 @@ "detailedCreativeTypeList": [ "904" ], + "h": 200, + "w": 200, "test": 1 } ], diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json index 1466737008f..c38bc9f69b9 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json @@ -93,7 +93,9 @@ "test": 1, "detailedCreativeTypeList": [ "903" - ] + ], + "h": 200, + "w": 200 } ], "device": { From 34ddf1a1adc46d88023d15a4b7b684921fdcc3c4 Mon Sep 17 00:00:00 2001 From: kmdevops <126434358+kmdevops@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:29:49 +0200 Subject: [PATCH 069/138] DXKulture adapter (#3176) co-authored: @kmdevops --- adapters/dxkulture/dxkulture.go | 170 ++++++++++++++++++ adapters/dxkulture/dxkulture_test.go | 17 ++ .../dxkulturetest/exemplary/banner.json | 144 +++++++++++++++ .../exemplary/empty-site-domain-ref.json | 142 +++++++++++++++ .../dxkulturetest/exemplary/ipv6.json | 142 +++++++++++++++ .../exemplary/video-test-request.json | 154 ++++++++++++++++ .../dxkulturetest/exemplary/video.json | 152 ++++++++++++++++ .../supplemental/invalid-imp-ext-bidder.json | 40 +++++ .../supplemental/invalid-imp-ext.json | 38 ++++ .../supplemental/invalid-response.json | 113 ++++++++++++ .../dxkulturetest/supplemental/no-mtype.json | 142 +++++++++++++++ .../supplemental/status-code-bad-request.json | 112 ++++++++++++ .../supplemental/status-code-no-content.json | 108 +++++++++++ .../supplemental/status-code-other-error.json | 112 ++++++++++++ adapters/dxkulture/params_test.go | 53 ++++++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_dxkulture.go | 7 + static/bidder-info/dxkulture.yaml | 16 ++ static/bidder-params/dxkulture.json | 20 +++ 20 files changed, 1686 insertions(+) create mode 100644 adapters/dxkulture/dxkulture.go create mode 100644 adapters/dxkulture/dxkulture_test.go create mode 100644 adapters/dxkulture/dxkulturetest/exemplary/banner.json create mode 100644 adapters/dxkulture/dxkulturetest/exemplary/empty-site-domain-ref.json create mode 100644 adapters/dxkulture/dxkulturetest/exemplary/ipv6.json create mode 100644 adapters/dxkulture/dxkulturetest/exemplary/video-test-request.json create mode 100644 adapters/dxkulture/dxkulturetest/exemplary/video.json create mode 100644 adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext-bidder.json create mode 100644 adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext.json create mode 100644 adapters/dxkulture/dxkulturetest/supplemental/invalid-response.json create mode 100644 adapters/dxkulture/dxkulturetest/supplemental/no-mtype.json create mode 100644 adapters/dxkulture/dxkulturetest/supplemental/status-code-bad-request.json create mode 100644 adapters/dxkulture/dxkulturetest/supplemental/status-code-no-content.json create mode 100644 adapters/dxkulture/dxkulturetest/supplemental/status-code-other-error.json create mode 100644 adapters/dxkulture/params_test.go create mode 100644 openrtb_ext/imp_dxkulture.go create mode 100644 static/bidder-info/dxkulture.yaml create mode 100644 static/bidder-params/dxkulture.json diff --git a/adapters/dxkulture/dxkulture.go b/adapters/dxkulture/dxkulture.go new file mode 100644 index 00000000000..5347c71a303 --- /dev/null +++ b/adapters/dxkulture/dxkulture.go @@ -0,0 +1,170 @@ +package dxkulture + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/prebid/openrtb/v19/openrtb2" +) + +var markupTypeToBidType = map[openrtb2.MarkupType]openrtb_ext.BidType{ + openrtb2.MarkupBanner: openrtb_ext.BidTypeBanner, + openrtb2.MarkupVideo: openrtb_ext.BidTypeVideo, +} + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the DXKulture adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + impressions := request.Imp + + adapterRequests := make([]*adapters.RequestData, 0, len(impressions)) + var errs []error + + for _, impression := range impressions { + impExt, err := parseExt(&impression) + if err != nil { + errs = append(errs, err) + continue + } + + request.Imp = []openrtb2.Imp{impression} + body, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + continue + } + + if request.Test == 1 { + impExt.PublisherId = "test" + } + + params := url.Values{} + params.Add("publisher_id", impExt.PublisherId) + params.Add("placement_id", impExt.PlacementId) + + adapterRequests = append(adapterRequests, &adapters.RequestData{ + Method: http.MethodPost, + Uri: a.endpoint + "?" + params.Encode(), + Body: body, + Headers: getHeaders(request), + }) + } + + request.Imp = impressions + return adapterRequests, errs +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(response) { + return nil, nil + } + if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { + return nil, []error{err} + } + + var ortbResponse openrtb2.BidResponse + err := json.Unmarshal(response.Body, &ortbResponse) + if err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bad Server Response", + }} + } + + var bidErrors []error + + bidderResponse := adapters.NewBidderResponseWithBidsCapacity(1) + for _, seatBid := range ortbResponse.SeatBid { + for i := range seatBid.Bid { + bid := seatBid.Bid[i] + bidType, err := getBidType(&bid) + if err != nil { + bidErrors = append(bidErrors, err) + continue + } + + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } + } + + return bidderResponse, bidErrors +} + +func getBidType(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { + if bidType, ok := markupTypeToBidType[bid.MType]; ok { + return bidType, nil + } + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unsupported MType %d", bid.MType), + } +} + +func parseExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpDXKulture, error) { + var bidderExt adapters.ExtImpBidder + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err), + } + } + + impExt := openrtb_ext.ExtImpDXKulture{} + err := json.Unmarshal(bidderExt.Bidder, &impExt) + if err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err), + } + } + + return &impExt, nil +} + +func getHeaders(request *openrtb2.BidRequest) http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + if request.Site != nil { + if request.Site.Ref != "" { + headers.Set("Referer", request.Site.Ref) + } + if request.Site.Domain != "" { + headers.Add("Origin", request.Site.Domain) + } + } + + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + + if len(request.Device.IPv6) > 0 { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } + + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + } + return headers +} diff --git a/adapters/dxkulture/dxkulture_test.go b/adapters/dxkulture/dxkulture_test.go new file mode 100644 index 00000000000..9b2da4f27ab --- /dev/null +++ b/adapters/dxkulture/dxkulture_test.go @@ -0,0 +1,17 @@ +package dxkulture + +import ( + "testing" + + "github.com/prebid/prebid-server/config" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder("dxkulture", config.Adapter{Endpoint: "https://ads.kulture.media/pbs"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + adapterstest.RunJSONBidderTest(t, "dxkulturetest", bidder) +} diff --git a/adapters/dxkulture/dxkulturetest/exemplary/banner.json b/adapters/dxkulture/dxkulturetest/exemplary/banner.json new file mode 100644 index 00000000000..3b84881fc67 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/exemplary/banner.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.kulture.media/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 1 + } + ], + "seat": "dxkulture" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/exemplary/empty-site-domain-ref.json b/adapters/dxkulture/dxkulturetest/exemplary/empty-site-domain-ref.json new file mode 100644 index 00000000000..630467ce96b --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/exemplary/empty-site-domain-ref.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "page": "http://site.com/page" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.kulture.media/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "page": "http://site.com/page" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + } + ], + "seat": "dxkulture" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/exemplary/ipv6.json b/adapters/dxkulture/dxkulturetest/exemplary/ipv6.json new file mode 100644 index 00000000000..58f7e7fdf2c --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/exemplary/ipv6.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ipv6": "2001:0000:130F:0000:0000:09C0:876A:130B" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "page": "http://site.com/page" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "2001:0000:130F:0000:0000:09C0:876A:130B" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.kulture.media/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ipv6": "2001:0000:130F:0000:0000:09C0:876A:130B" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "page": "http://site.com/page" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + } + ], + "seat": "dxkulture" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/exemplary/video-test-request.json b/adapters/dxkulture/dxkulturetest/exemplary/video-test-request.json new file mode 100644 index 00000000000..6a0746ad258 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/exemplary/video-test-request.json @@ -0,0 +1,154 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 1, + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.kulture.media/pbs?placement_id=placement123&publisher_id=test", + "body": { + "id": "test-request-id", + "test": 1, + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + } + ], + "seat": "dxkulture" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/exemplary/video.json b/adapters/dxkulture/dxkulturetest/exemplary/video.json new file mode 100644 index 00000000000..017dd5ea2bc --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/exemplary/video.json @@ -0,0 +1,152 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.kulture.media/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + } + ], + "seat": "dxkulture" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext-bidder.json b/adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext-bidder.json new file mode 100644 index 00000000000..ae30b327030 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext-bidder.json @@ -0,0 +1,40 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": "not_json" + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Ignoring imp id=test-imp-id, error while decoding impExt, err: json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpDXKulture", + "comparison": "literal" + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext.json b/adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext.json new file mode 100644 index 00000000000..2587dc216d2 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext.json @@ -0,0 +1,38 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": "not_json" + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Ignoring imp id=test-imp-id, error while decoding extImpBidder, err: json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/invalid-response.json b/adapters/dxkulture/dxkulturetest/supplemental/invalid-response.json new file mode 100644 index 00000000000..8fff1bb0375 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/invalid-response.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.kulture.media/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 200, + "body": "body" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad Server Response", + "comparison": "literal" + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/no-mtype.json b/adapters/dxkulture/dxkulturetest/supplemental/no-mtype.json new file mode 100644 index 00000000000..a56200fad50 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/no-mtype.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.kulture.media/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "" + } + ], + "seat": "dxkulture" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unsupported MType 0", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ] + +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/status-code-bad-request.json b/adapters/dxkulture/dxkulturetest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..f0f2ce2c47b --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/status-code-bad-request.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.kulture.media/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/status-code-no-content.json b/adapters/dxkulture/dxkulturetest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..43c53f3d64c --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/status-code-no-content.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.kulture.media/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/status-code-other-error.json b/adapters/dxkulture/dxkulturetest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..3e1b0b33c1e --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/status-code-other-error.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.kulture.media/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 505 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 505. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/dxkulture/params_test.go b/adapters/dxkulture/params_test.go new file mode 100644 index 00000000000..838d092df8f --- /dev/null +++ b/adapters/dxkulture/params_test.go @@ -0,0 +1,53 @@ +package dxkulture + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/dxkulture.json +// +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.dxkulture + +// TestValidParams makes sure that the dxkulture schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderDXKulture, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected dxkulture params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the dxkulture schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderDXKulture, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"publisherId": "pub", "placementId": "plac"}`, + `{"publisherId": "pub", "placementId": "plac", "a":1}`, +} + +var invalidParams = []string{ + `{"publisherId": "pub"}`, + `{"placementId": "plac"}`, + //malformed + `{"ub", "placementId": "plac"}`, + `{}`, +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index d6b4400cd05..6ab0dccfc7c 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -73,6 +73,7 @@ import ( "github.com/prebid/prebid-server/adapters/deepintent" "github.com/prebid/prebid-server/adapters/dianomi" "github.com/prebid/prebid-server/adapters/dmx" + "github.com/prebid/prebid-server/adapters/dxkulture" evolution "github.com/prebid/prebid-server/adapters/e_volution" "github.com/prebid/prebid-server/adapters/edge226" "github.com/prebid/prebid-server/adapters/emtv" @@ -266,6 +267,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderDianomi: dianomi.Builder, openrtb_ext.BidderEdge226: edge226.Builder, openrtb_ext.BidderDmx: dmx.Builder, + openrtb_ext.BidderDXKulture: dxkulture.Builder, openrtb_ext.BidderEmtv: emtv.Builder, openrtb_ext.BidderEmxDigital: cadentaperturemx.Builder, openrtb_ext.BidderEPlanning: eplanning.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index ae0f7b6c82a..22a915b769f 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -90,6 +90,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderDianomi, BidderEdge226, BidderDmx, + BidderDXKulture, BidderEmtv, BidderEmxDigital, BidderEPlanning, @@ -370,6 +371,7 @@ const ( BidderDianomi BidderName = "dianomi" BidderEdge226 BidderName = "edge226" BidderDmx BidderName = "dmx" + BidderDXKulture BidderName = "dxkulture" BidderEmtv BidderName = "emtv" BidderEmxDigital BidderName = "emx_digital" BidderEPlanning BidderName = "eplanning" diff --git a/openrtb_ext/imp_dxkulture.go b/openrtb_ext/imp_dxkulture.go new file mode 100644 index 00000000000..4b507c55248 --- /dev/null +++ b/openrtb_ext/imp_dxkulture.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpDXKulture defines the contract for bidrequest.imp[i].ext.prebid.bidder.dxkulture +type ExtImpDXKulture struct { + PublisherId string `json:"publisherId"` + PlacementId string `json:"placementId"` +} diff --git a/static/bidder-info/dxkulture.yaml b/static/bidder-info/dxkulture.yaml new file mode 100644 index 00000000000..1af72e9fc33 --- /dev/null +++ b/static/bidder-info/dxkulture.yaml @@ -0,0 +1,16 @@ +endpoint: "https://ads.kulture.media/pbs" +maintainer: + email: "devops@kulture.media" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + redirect: + url: "https://ads.kulture.media/usync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-params/dxkulture.json b/static/bidder-params/dxkulture.json new file mode 100644 index 00000000000..858eefd22e4 --- /dev/null +++ b/static/bidder-params/dxkulture.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "DXKulture Adapter Params", + "description": "A schema which validates params accepted by the DXKulture adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "The publisher id" + }, + "placementId": { + "type": "string", + "description": "The placement id" + } + }, + "required": [ + "publisherId", + "placementId" + ] +} From 6f630fd80915492fba577be7868f1eaa8b0cf51a Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Thu, 19 Oct 2023 13:43:00 -0400 Subject: [PATCH 070/138] JSON Serialization: Change Libraries (#3225) --- account/account.go | 9 +- adservertargeting/adservertargeting_test.go | 7 +- adservertargeting/reqcache.go | 5 +- adservertargeting/respdataprocessor.go | 3 +- analytics/filesystem/file_module.go | 14 +- analytics/pubstack/helpers/json.go | 12 +- config/config.go | 3 +- currency/rate_converter.go | 4 +- currency/rates_test.go | 27 ++-- endpoints/cookie_sync.go | 3 +- endpoints/cookie_sync_test.go | 6 +- endpoints/currency_rates.go | 4 +- endpoints/events/vtrack.go | 7 +- endpoints/events/vtrack_test.go | 5 +- endpoints/info/bidders.go | 11 +- endpoints/info/bidders_detail.go | 5 +- endpoints/openrtb2/amp_auction.go | 11 +- endpoints/openrtb2/amp_auction_test.go | 61 ++++---- endpoints/openrtb2/auction.go | 33 ++-- endpoints/openrtb2/auction_test.go | 43 +++--- .../invalid-stored/bad_incoming_1.json | 2 +- .../invalid-stored/bad_incoming_imp.json | 2 +- .../invalid-stored/bad_stored_imp.json | 2 +- .../invalid-stored/bad_stored_req.json | 2 +- .../invalid-whole/interstital-bad-perc.json | 2 +- .../invalid-whole/invalid-source.json | 2 +- .../invalid-whole/malformed-bid-request.json | 2 +- .../invalid-whole/regs-ext-malformed.json | 2 +- .../invalid-whole/user-ext-consent-int.json | 2 +- .../user-gdpr-consent-invalid.json | 2 +- endpoints/openrtb2/test_utils.go | 15 +- endpoints/openrtb2/video_auction.go | 18 +-- endpoints/openrtb2/video_auction_test.go | 21 +-- endpoints/setuid_test.go | 4 +- endpoints/version.go | 3 +- errortypes/code.go | 2 + errortypes/errortypes.go | 34 +++++ exchange/auction.go | 10 +- exchange/auction_test.go | 10 +- exchange/bidder.go | 9 +- exchange/bidder_test.go | 7 +- exchange/events.go | 4 +- exchange/exchange.go | 17 ++- exchange/exchange_test.go | 69 ++++----- exchange/targeting_test.go | 5 +- exchange/utils.go | 39 ++--- exchange/utils_test.go | 53 +++---- firstpartydata/first_party_data.go | 7 +- firstpartydata/first_party_data_test.go | 133 +++++++++-------- floors/floors_test.go | 3 +- floors/rule_test.go | 2 +- gdpr/full_enforcement_test.go | 4 +- gdpr/vendorlist-fetching_test.go | 4 +- go.mod | 3 + go.sum | 3 + hooks/hookanalytics/analytics_test.go | 4 +- hooks/hookexecution/enricher.go | 5 +- hooks/hookexecution/enricher_test.go | 5 +- hooks/hookexecution/test_utils.go | 5 +- hooks/plan_test.go | 18 +-- modules/modules.go | 3 +- modules/modules_test.go | 2 +- modules/prebid/ortb2blocking/config.go | 5 +- modules/prebid/ortb2blocking/module_test.go | 2 +- openrtb_ext/convert_down_test.go | 99 ++++++------ openrtb_ext/convert_up_test.go | 12 +- openrtb_ext/deal_tier.go | 5 +- openrtb_ext/deal_tier_test.go | 21 +-- openrtb_ext/device_test.go | 25 ++-- openrtb_ext/imp_appnexus.go | 6 +- openrtb_ext/imp_appnexus_test.go | 6 +- openrtb_ext/request.go | 5 +- openrtb_ext/request_test.go | 3 +- openrtb_ext/request_wrapper.go | 141 ++++++++++-------- openrtb_ext/request_wrapper_test.go | 25 ++-- openrtb_ext/site_test.go | 18 +-- ortb/default_test.go | 9 +- prebid_cache_client/client_test.go | 3 +- privacy/scrubber.go | 9 +- router/router.go | 5 +- router/router_test.go | 3 +- schain/schainwriter.go | 5 +- schain/schainwriter_test.go | 3 +- .../backends/file_fetcher/fetcher.go | 3 +- .../backends/file_fetcher/fetcher_test.go | 7 +- .../backends/http_fetcher/fetcher.go | 10 +- .../backends/http_fetcher/fetcher_test.go | 7 +- stored_requests/events/api/api.go | 6 +- stored_requests/events/http/http.go | 3 +- stored_requests/events/http/http_test.go | 6 +- stored_responses/stored_responses.go | 3 +- usersync/cookie.go | 6 +- usersync/decoder.go | 8 +- usersync/encoder.go | 5 +- util/jsonutil/jsonutil.go | 113 +++++++++++++- util/jsonutil/jsonutil_test.go | 61 +++++++- util/jsonutil/stringInt_test.go | 11 +- version/xprebidheader_test.go | 6 +- 98 files changed, 862 insertions(+), 617 deletions(-) diff --git a/account/account.go b/account/account.go index b4d0c334d6b..1f5a3feee14 100644 --- a/account/account.go +++ b/account/account.go @@ -2,7 +2,6 @@ package account import ( "context" - "encoding/json" "fmt" "github.com/prebid/go-gdpr/consentconstants" @@ -13,6 +12,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/util/jsonutil" ) // GetAccount looks up the config.Account object referenced by the given accountID, with access rules applied @@ -44,17 +44,12 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r } else { // accountID resolved to a valid account, merge with AccountDefaults for a complete config account = &config.Account{} - err := json.Unmarshal(accountJSON, account) - if _, ok := err.(*json.UnmarshalTypeError); ok { + if err := jsonutil.UnmarshalValid(accountJSON, account); err != nil { return nil, []error{&errortypes.MalformedAcct{ Message: fmt.Sprintf("The prebid-server account config for account id \"%s\" is malformed. Please reach out to the prebid server host.", accountID), }} } - if err != nil { - errs = append(errs, err) - return nil, errs - } // Fill in ID if needed, so it can be left out of account definition if len(account.ID) == 0 { account.ID = accountID diff --git a/adservertargeting/adservertargeting_test.go b/adservertargeting/adservertargeting_test.go index 4a651fdd2be..0da3635d9b7 100644 --- a/adservertargeting/adservertargeting_test.go +++ b/adservertargeting/adservertargeting_test.go @@ -8,6 +8,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -42,7 +43,7 @@ func TestExtractAdServerTargeting(t *testing.T) { p := "https://www.test-url.com?ampkey=testAmpKey&data-override-height=400" u, _ := url.Parse(p) params := u.Query() - reqBytes, err := json.Marshal(r) + reqBytes, err := jsonutil.Marshal(r) assert.NoError(t, err, "unexpected req marshal error") res, warnings := collect(rw, reqBytes, params) @@ -248,7 +249,7 @@ func TestProcessAdServerTargetingFull(t *testing.T) { bidResponseExt := &openrtb_ext.ExtBidResponse{Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)} - reqBytes, err := json.Marshal(r) + reqBytes, err := jsonutil.Marshal(r) assert.NoError(t, err, "unexpected req marshal error") targetingKeyLen := 0 resResp := Apply(rw, reqBytes, resp, params, bidResponseExt, &targetingKeyLen) @@ -331,7 +332,7 @@ func TestProcessAdServerTargetingWarnings(t *testing.T) { bidResponseExt := &openrtb_ext.ExtBidResponse{Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)} - reqBytes, err := json.Marshal(r) + reqBytes, err := jsonutil.Marshal(r) assert.NoError(t, err, "unexpected req marshal error") resResp := Apply(rw, reqBytes, resp, params, bidResponseExt, nil) assert.Len(t, resResp.SeatBid, 2, "Incorrect response: seat bid number") diff --git a/adservertargeting/reqcache.go b/adservertargeting/reqcache.go index cb2edac9e4a..b8b147af84e 100644 --- a/adservertargeting/reqcache.go +++ b/adservertargeting/reqcache.go @@ -5,6 +5,7 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/util/jsonutil" ) type requestCache struct { @@ -24,7 +25,7 @@ func (reqImpCache *requestCache) GetImpsData() ([]json.RawMessage, error) { } var impsData []json.RawMessage - err = json.Unmarshal(imps, &impsData) + err = jsonutil.Unmarshal(imps, &impsData) if err != nil { return nil, err } @@ -48,7 +49,7 @@ func (bidsCache *bidsCache) GetBid(bidderName, bidId string, bid openrtb2.Bid) ( } _, bidExists := bidsCache.bids[bidderName][bidId] if !bidExists { - bidBytes, err := json.Marshal(bid) + bidBytes, err := jsonutil.Marshal(bid) if err != nil { return nil, err } diff --git a/adservertargeting/respdataprocessor.go b/adservertargeting/respdataprocessor.go index 94a391d08bb..649f802f6e2 100644 --- a/adservertargeting/respdataprocessor.go +++ b/adservertargeting/respdataprocessor.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) @@ -79,7 +80,7 @@ func buildBidExt(targetingData map[string]string, Targeting: targetingDataTruncated, }, } - bidExtTargeting, err := json.Marshal(bidExtTargetingData) + bidExtTargeting, err := jsonutil.Marshal(bidExtTargetingData) if err != nil { warnings = append(warnings, createWarning(err.Error())) return nil diff --git a/analytics/filesystem/file_module.go b/analytics/filesystem/file_module.go index f055e4470b1..24b7be6b599 100644 --- a/analytics/filesystem/file_module.go +++ b/analytics/filesystem/file_module.go @@ -2,12 +2,12 @@ package filesystem import ( "bytes" - "encoding/json" "fmt" "github.com/chasex/glog" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/util/jsonutil" ) type RequestType string @@ -120,7 +120,7 @@ func jsonifyAuctionObject(ao *analytics.AuctionObject) string { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Type RequestType `json:"type"` *logAuction }{ @@ -153,7 +153,7 @@ func jsonifyVideoObject(vo *analytics.VideoObject) string { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Type RequestType `json:"type"` *logVideo }{ @@ -178,7 +178,7 @@ func jsonifyCookieSync(cso *analytics.CookieSyncObject) string { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Type RequestType `json:"type"` *logUserSync }{ @@ -205,7 +205,7 @@ func jsonifySetUIDObject(so *analytics.SetUIDObject) string { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Type RequestType `json:"type"` *logSetUID }{ @@ -239,7 +239,7 @@ func jsonifyAmpObject(ao *analytics.AmpObject) string { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Type RequestType `json:"type"` *logAMP }{ @@ -263,7 +263,7 @@ func jsonifyNotificationEventObject(ne *analytics.NotificationEvent) string { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Type RequestType `json:"type"` *logNotificationEvent }{ diff --git a/analytics/pubstack/helpers/json.go b/analytics/pubstack/helpers/json.go index 368c79e3f6a..f56a4c6194b 100644 --- a/analytics/pubstack/helpers/json.go +++ b/analytics/pubstack/helpers/json.go @@ -1,11 +1,11 @@ package helpers import ( - "encoding/json" "fmt" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/util/jsonutil" ) func JsonifyAuctionObject(ao *analytics.AuctionObject, scope string) ([]byte, error) { @@ -26,7 +26,7 @@ func JsonifyAuctionObject(ao *analytics.AuctionObject, scope string) ([]byte, er } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Scope string `json:"scope"` *logAuction }{ @@ -59,7 +59,7 @@ func JsonifyVideoObject(vo *analytics.VideoObject, scope string) ([]byte, error) } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Scope string `json:"scope"` *logVideo }{ @@ -84,7 +84,7 @@ func JsonifyCookieSync(cso *analytics.CookieSyncObject, scope string) ([]byte, e } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Scope string `json:"scope"` *logUserSync }{ @@ -111,7 +111,7 @@ func JsonifySetUIDObject(so *analytics.SetUIDObject, scope string) ([]byte, erro } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Scope string `json:"scope"` *logSetUID }{ @@ -145,7 +145,7 @@ func JsonifyAmpObject(ao *analytics.AmpObject, scope string) ([]byte, error) { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Scope string `json:"scope"` *logAMP }{ diff --git a/config/config.go b/config/config.go index cf3591dd564..4bd7f807002 100644 --- a/config/config.go +++ b/config/config.go @@ -14,6 +14,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/spf13/viper" ) @@ -775,7 +776,7 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin // MarshalAccountDefaults compiles AccountDefaults into the JSON format used for merge patch func (cfg *Configuration) MarshalAccountDefaults() error { var err error - if cfg.accountDefaultsJSON, err = json.Marshal(cfg.AccountDefaults); err != nil { + if cfg.accountDefaultsJSON, err = jsonutil.Marshal(cfg.AccountDefaults); err != nil { glog.Warningf("converting %+v to json: %v", cfg.AccountDefaults, err) } return err diff --git a/currency/rate_converter.go b/currency/rate_converter.go index f28807701ae..cda8d763048 100644 --- a/currency/rate_converter.go +++ b/currency/rate_converter.go @@ -1,7 +1,6 @@ package currency import ( - "encoding/json" "fmt" "io" "net/http" @@ -10,6 +9,7 @@ import ( "github.com/golang/glog" "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/timeutil" ) @@ -66,7 +66,7 @@ func (rc *RateConverter) fetch() (*Rates, error) { } updatedRates := &Rates{} - err = json.Unmarshal(bytesJSON, updatedRates) + err = jsonutil.UnmarshalValid(bytesJSON, updatedRates) if err != nil { return nil, err } diff --git a/currency/rates_test.go b/currency/rates_test.go index 23226dce8fb..86c25d14ac0 100644 --- a/currency/rates_test.go +++ b/currency/rates_test.go @@ -1,11 +1,12 @@ package currency import ( - "encoding/json" "errors" "testing" "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/util/jsonutil" ) func TestUnMarshallRates(t *testing.T) { @@ -22,7 +23,7 @@ func TestUnMarshallRates(t *testing.T) { ratesJSON: `malformed`, expectedRates: Rates{}, expectsError: true, - expectedError: errors.New("invalid character 'm' looking for beginning of value"), + expectedError: errors.New("expect { or n, but found m"), }, { desc: "Valid JSON field defining valid conversion object. Expect no error", @@ -50,7 +51,7 @@ func TestUnMarshallRates(t *testing.T) { expectedError: nil, }, { - desc: "Valid JSON field defines a conversions map with repeated entries, expect error", + desc: "Valid JSON field defines a conversions map with repeated entries, last one wins", ratesJSON: `{ "conversions":{ "USD":{ @@ -58,25 +59,31 @@ func TestUnMarshallRates(t *testing.T) { "MXN":20.00 }, "USD":{ - "GBP":0.7662523901 - }, + "GBP":0.4815162342 + } } }`, - expectedRates: Rates{}, - expectsError: true, - expectedError: errors.New("invalid character '}' looking for beginning of object key string"), + expectedRates: Rates{ + Conversions: map[string]map[string]float64{ + "USD": { + "GBP": 0.4815162342, + }, + }, + }, + expectsError: false, + expectedError: nil, }, } for _, tc := range testCases { // Execute: updatedRates := Rates{} - err := json.Unmarshal([]byte(tc.ratesJSON), &updatedRates) + err := jsonutil.UnmarshalValid([]byte(tc.ratesJSON), &updatedRates) // Verify: assert.Equal(t, err != nil, tc.expectsError, tc.desc) if tc.expectsError { - assert.Equal(t, err.Error(), tc.expectedError.Error(), tc.desc) + assert.Equal(t, tc.expectedError.Error(), err.Error(), tc.desc) } assert.Equal(t, tc.expectedRates, updatedRates, tc.desc) } diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index 24530efa56a..ff801de988f 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -27,6 +27,7 @@ import ( gppPrivacy "github.com/prebid/prebid-server/privacy/gpp" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/jsonutil" stringutil "github.com/prebid/prebid-server/util/stringutil" ) @@ -119,7 +120,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma } request := cookieSyncRequest{} - if err := json.Unmarshal(body, &request); err != nil { + if err := jsonutil.UnmarshalValid(body, &request); err != nil { return usersync.Request{}, macros.UserSyncPrivacy{}, fmt.Errorf("JSON parsing failed: %s", err.Error()) } diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index af7819b94b9..1b02357dc5d 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -190,14 +190,14 @@ func TestCookieSyncHandle(t *testing.T) { SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, }, expectedStatusCode: 400, - expectedBody: `JSON parsing failed: invalid character 'm' looking for beginning of value` + "\n", + expectedBody: `JSON parsing failed: expect { or n, but found m` + "\n", setMetricsExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordCookieSync", metrics.CookieSyncBadRequest).Once() }, setAnalyticsExpectations: func(a *MockAnalyticsRunner) { expected := analytics.CookieSyncObject{ Status: 400, - Errors: []error{errors.New("JSON parsing failed: invalid character 'm' looking for beginning of value")}, + Errors: []error{errors.New("JSON parsing failed: expect { or n, but found m")}, BidderStatus: []*analytics.CookieSyncBidder{}, } a.On("LogCookieSyncObject", &expected).Once() @@ -805,7 +805,7 @@ func TestCookieSyncParseRequest(t *testing.T) { givenBody: strings.NewReader(`malformed`), givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, - expectedError: "JSON parsing failed: invalid character 'm' looking for beginning of value", + expectedError: "JSON parsing failed: expect { or n, but found m", }, { description: "Invalid Type Filter", diff --git a/endpoints/currency_rates.go b/endpoints/currency_rates.go index d35cb74cea4..49ae9963cd9 100644 --- a/endpoints/currency_rates.go +++ b/endpoints/currency_rates.go @@ -1,12 +1,12 @@ package endpoints import ( - "encoding/json" "net/http" "time" "github.com/golang/glog" "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/util/jsonutil" ) // currencyRatesInfo holds currency rates information. @@ -60,7 +60,7 @@ func NewCurrencyRatesEndpoint(rateConverter rateConverter, fetchingInterval time currencyRateInfo := newCurrencyRatesInfo(rateConverter, fetchingInterval) return func(w http.ResponseWriter, _ *http.Request) { - jsonOutput, err := json.Marshal(currencyRateInfo) + jsonOutput, err := jsonutil.Marshal(currencyRateInfo) if err != nil { glog.Errorf("/currency/rates Critical error when trying to marshal currencyRateInfo: %v", err) w.WriteHeader(http.StatusInternalServerError) diff --git a/endpoints/events/vtrack.go b/endpoints/events/vtrack.go index e0bf3fd4be8..eeb409e24ae 100644 --- a/endpoints/events/vtrack.go +++ b/endpoints/events/vtrack.go @@ -19,6 +19,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/util/jsonutil" ) const ( @@ -123,7 +124,7 @@ func (v *vtrackEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httpro } } - d, err := json.Marshal(*cachingResponse) + d, err := jsonutil.Marshal(*cachingResponse) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -187,7 +188,7 @@ func ParseVTrackRequest(httpRequest *http.Request, maxRequestSize int64) (req *B return req, err } - if err := json.Unmarshal(requestJson, req); err != nil { + if err := jsonutil.UnmarshalValid(requestJson, req); err != nil { return req, err } @@ -319,7 +320,7 @@ func ModifyVastXmlString(externalUrl, vast, bidid, bidder, accountID string, tim // ModifyVastXmlJSON modifies BidCacheRequest element Vast XML data func ModifyVastXmlJSON(externalUrl string, data json.RawMessage, bidid, bidder, accountId string, timestamp int64, integrationType string) json.RawMessage { var vast string - if err := json.Unmarshal(data, &vast); err != nil { + if err := jsonutil.Unmarshal(data, &vast); err != nil { // failed to decode json, fall back to string vast = string(data) } diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index 1e36a9e627f..d950a443afe 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -16,6 +16,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -186,7 +187,7 @@ func TestShouldRespondWithBadRequestWhenBidIdIsMissing(t *testing.T) { }, } - reqData, err := json.Marshal(data) + reqData, err := jsonutil.Marshal(data) if err != nil { t.Fatal(err) } @@ -239,7 +240,7 @@ func TestShouldRespondWithBadRequestWhenBidderIsMissing(t *testing.T) { }, } - reqData, err := json.Marshal(data) + reqData, err := jsonutil.Marshal(data) if err != nil { t.Fatal(err) } diff --git a/endpoints/info/bidders.go b/endpoints/info/bidders.go index 9984abe216d..bd6d078b3ba 100644 --- a/endpoints/info/bidders.go +++ b/endpoints/info/bidders.go @@ -1,7 +1,6 @@ package info import ( - "encoding/json" "net/http" "sort" "strings" @@ -9,6 +8,7 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/util/jsonutil" ) var invalidEnabledOnlyMsg = []byte(`Invalid value for 'enabledonly' query param, must be of boolean type`) @@ -103,7 +103,7 @@ func prepareBiddersResponseAll(bidders config.BidderInfos, aliases map[string]st } sort.Strings(bidderNames) - return json.Marshal(bidderNames) + return jsonutil.Marshal(bidderNames) } func prepareBiddersResponseAllBaseOnly(bidders config.BidderInfos) ([]byte, error) { @@ -116,7 +116,7 @@ func prepareBiddersResponseAllBaseOnly(bidders config.BidderInfos) ([]byte, erro } sort.Strings(bidderNames) - return json.Marshal(bidderNames) + return jsonutil.Marshal(bidderNames) } func prepareBiddersResponseEnabledOnly(bidders config.BidderInfos, aliases map[string]string) ([]byte, error) { @@ -135,8 +135,7 @@ func prepareBiddersResponseEnabledOnly(bidders config.BidderInfos, aliases map[s } sort.Strings(bidderNames) - - return json.Marshal(bidderNames) + return jsonutil.Marshal(bidderNames) } func prepareBiddersResponseEnabledOnlyBaseOnly(bidders config.BidderInfos) ([]byte, error) { @@ -149,7 +148,7 @@ func prepareBiddersResponseEnabledOnlyBaseOnly(bidders config.BidderInfos) ([]by } sort.Strings(bidderNames) - return json.Marshal(bidderNames) + return jsonutil.Marshal(bidderNames) } func writeBadRequest(w http.ResponseWriter, data []byte) { diff --git a/endpoints/info/bidders_detail.go b/endpoints/info/bidders_detail.go index d9dc776f50d..34d14efde1b 100644 --- a/endpoints/info/bidders_detail.go +++ b/endpoints/info/bidders_detail.go @@ -10,6 +10,7 @@ import ( "github.com/julienschmidt/httprouter" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" ) const ( @@ -101,7 +102,7 @@ func marshalDetailsResponse(details map[string]bidderDetail) (map[string][]byte, responses := map[string][]byte{} for bidder, detail := range details { - json, err := json.Marshal(detail) + json, err := jsonutil.Marshal(detail) if err != nil { return nil, fmt.Errorf("unable to marshal info for bidder %s: %v", bidder, err) } @@ -118,7 +119,7 @@ func marshalAllResponse(responses map[string][]byte) ([]byte, error) { responsesJSON[k] = json.RawMessage(v) } - json, err := json.Marshal(responsesJSON) + json, err := jsonutil.Marshal(responsesJSON) if err != nil { return nil, fmt.Errorf("unable to marshal info for bidder all: %v", err) } diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 2fde59c25e4..76b0e83cf3d 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -37,6 +37,7 @@ import ( "github.com/prebid/prebid-server/stored_responses" "github.com/prebid/prebid-server/usersync" "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/version" ) @@ -341,7 +342,7 @@ func sendAmpResponse( // but this is a very unlikely corner case. Doing this so we can catch "hb_cache_id" // and "hb_cache_id_{deal}", which allows for deal support in AMP. bidExt := &openrtb_ext.ExtBid{} - err := json.Unmarshal(bid.Ext, bidExt) + err := jsonutil.Unmarshal(bid.Ext, bidExt) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Critical error while unpacking AMP targets: %v", err) @@ -360,7 +361,7 @@ func sendAmpResponse( // Extract global targeting var extResponse openrtb_ext.ExtBidResponse - eRErr := json.Unmarshal(response.Ext, &extResponse) + eRErr := jsonutil.Unmarshal(response.Ext, &extResponse) if eRErr != nil { ao.Errors = append(ao.Errors, fmt.Errorf("AMP response: failed to unpack OpenRTB response.ext, debug info cannot be forwarded: %v", eRErr)) } @@ -409,7 +410,7 @@ func getExtBidResponse( } // Extract any errors var extResponse openrtb_ext.ExtBidResponse - eRErr := json.Unmarshal(response.Ext, &extResponse) + eRErr := jsonutil.Unmarshal(response.Ext, &extResponse) if eRErr != nil { ao.Errors = append(ao.Errors, fmt.Errorf("AMP response: failed to unpack OpenRTB response.ext, debug info cannot be forwarded: %v", eRErr)) } @@ -524,7 +525,7 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req // The fetched config becomes the entire OpenRTB request requestJSON := storedRequests[ampParams.StoredRequestID] - if err := json.Unmarshal(requestJSON, req); err != nil { + if err := jsonutil.UnmarshalValid(requestJSON, req); err != nil { errs = []error{err} return } @@ -824,7 +825,7 @@ func setTrace(req *openrtb2.BidRequest, value string) error { return nil } - ext, err := json.Marshal(map[string]map[string]string{"prebid": {"trace": value}}) + ext, err := jsonutil.Marshal(map[string]map[string]string{"prebid": {"trace": value}}) if err != nil { return err } diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 4315ec7926f..7de8995754a 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -32,6 +32,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/util/jsonutil" ) // TestGoodRequests makes sure that the auction runs properly-formatted stored bids correctly. @@ -77,7 +78,7 @@ func TestGoodAmpRequests(t *testing.T) { } test := testCase{} - if !assert.NoError(t, json.Unmarshal(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", filename, err) { + if !assert.NoError(t, jsonutil.UnmarshalValid(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", filename, err) { continue } @@ -130,7 +131,7 @@ func TestGoodAmpRequests(t *testing.T) { } if test.ExpectedValidatedBidReq != nil { // compare as json to ignore whitespace and ext field ordering - actualJson, err := json.Marshal(ex.actualValidatedBidReq) + actualJson, err := jsonutil.Marshal(ex.actualValidatedBidReq) if assert.NoError(t, err, "Error converting actual bid request to json. Test file: %s", filename) { assert.JSONEq(t, string(test.ExpectedValidatedBidReq), string(actualJson), "Not the expected validated request. Test file: %s", filename) } @@ -159,7 +160,7 @@ func TestAccountErrors(t *testing.T) { } test := testCase{} - if !assert.NoError(t, json.Unmarshal(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", tt.filename, err) { + if !assert.NoError(t, jsonutil.UnmarshalValid(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", tt.filename, err) { continue } test.StoredRequest = map[string]json.RawMessage{tt.storedReqID: test.BidRequest} @@ -325,7 +326,7 @@ func TestGDPRConsent(t *testing.T) { // Parse Response var response AmpResponse - if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } @@ -341,7 +342,7 @@ func TestGDPRConsent(t *testing.T) { return } var ue openrtb_ext.ExtUser - err = json.Unmarshal(result.User.Ext, &ue) + err = jsonutil.UnmarshalValid(result.User.Ext, &ue) if !assert.NoError(t, err, test.description+":deserialize") { return } @@ -356,7 +357,7 @@ func TestGDPRConsent(t *testing.T) { // Parse Resonse var responseLegacy AmpResponse - if err := json.Unmarshal(responseRecorderLegacy.Body.Bytes(), &responseLegacy); err != nil { + if err := jsonutil.UnmarshalValid(responseRecorderLegacy.Body.Bytes(), &responseLegacy); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } @@ -372,7 +373,7 @@ func TestGDPRConsent(t *testing.T) { return } var ueLegacy openrtb_ext.ExtUser - err = json.Unmarshal(resultLegacy.User.Ext, &ueLegacy) + err = jsonutil.UnmarshalValid(resultLegacy.User.Ext, &ueLegacy) if !assert.NoError(t, err, test.description+":legacy:deserialize") { return } @@ -519,7 +520,7 @@ func TestOverrideWithParams(t *testing.T) { Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}, User: &openrtb2.User{Ext: json.RawMessage(`malformed`)}, }, - errorMsgs: []string{"invalid character 'm' looking for beginning of value"}, + errorMsgs: []string{"expect { or n, but found m"}, expectFatalErrors: true, }, }, @@ -567,7 +568,7 @@ func TestOverrideWithParams(t *testing.T) { User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)}, Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}, }, - errorMsgs: []string{"invalid character 'm' looking for beginning of object key string"}, + errorMsgs: []string{"expect \" after {, but found m"}, expectFatalErrors: true, }, }, @@ -749,7 +750,7 @@ func TestCCPAConsent(t *testing.T) { // Parse Response var response AmpResponse - if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } @@ -765,7 +766,7 @@ func TestCCPAConsent(t *testing.T) { return } var re openrtb_ext.ExtRegs - err = json.Unmarshal(result.Regs.Ext, &re) + err = jsonutil.UnmarshalValid(result.Regs.Ext, &re) if !assert.NoError(t, err, test.description+":deserialize") { return } @@ -871,7 +872,7 @@ func TestConsentWarnings(t *testing.T) { // Parse Response var response AmpResponse - if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } @@ -962,7 +963,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { // Parse Response var response AmpResponse - if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } @@ -978,7 +979,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { return } var ue openrtb_ext.ExtUser - err = json.Unmarshal(result.User.Ext, &ue) + err = jsonutil.UnmarshalValid(result.User.Ext, &ue) if !assert.NoError(t, err, test.description+":deserialize") { return } @@ -1099,7 +1100,7 @@ func TestAmpDebug(t *testing.T) { } var response AmpResponse - if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(recorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } @@ -1134,7 +1135,7 @@ func TestInitAmpTargetingAndCache(t *testing.T) { { name: "malformed", request: &openrtb2.BidRequest{Ext: json.RawMessage("malformed")}, - expectedErrs: []string{"invalid character 'm' looking for beginning of value"}, + expectedErrs: []string{"expect { or n, but found m"}, }, { name: "nil", @@ -1240,12 +1241,12 @@ func TestQueryParamOverrides(t *testing.T) { } var response AmpResponse - if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(recorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } var resolvedRequest openrtb2.BidRequest - err := json.Unmarshal(response.ORTB2.Ext.Debug.ResolvedRequest, &resolvedRequest) + err := jsonutil.UnmarshalValid(response.ORTB2.Ext.Debug.ResolvedRequest, &resolvedRequest) assert.NoError(t, err, "resolved request should have a correct format") if resolvedRequest.TMax != timeout { t.Errorf("Expected TMax to equal timeout (%d), got: %d", timeout, resolvedRequest.TMax) @@ -1391,11 +1392,11 @@ func (s formatOverrideSpec) execute(t *testing.T) { t.Errorf("Request was: %s", string(requests["1"])) } var response AmpResponse - if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(recorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } var resolvedRequest openrtb2.BidRequest - err := json.Unmarshal(response.ORTB2.Ext.Debug.ResolvedRequest, &resolvedRequest) + err := jsonutil.UnmarshalValid(response.ORTB2.Ext.Debug.ResolvedRequest, &resolvedRequest) assert.NoError(t, err, "resolved request should have the correct format") formats := resolvedRequest.Imp[0].Banner.Format if len(formats) != len(s.expect) { @@ -1445,14 +1446,14 @@ func (m *mockAmpExchange) HoldAuction(ctx context.Context, auctionRequest *excha if len(auctionRequest.StoredAuctionResponses) > 0 { var seatBids []openrtb2.SeatBid - if err := json.Unmarshal(auctionRequest.StoredAuctionResponses[r.BidRequest.Imp[0].ID], &seatBids); err != nil { + if err := jsonutil.UnmarshalValid(auctionRequest.StoredAuctionResponses[r.BidRequest.Imp[0].ID], &seatBids); err != nil { return nil, err } response.SeatBid = seatBids } if r.BidRequest.Test == 1 { - resolvedRequest, err := json.Marshal(r.BidRequest) + resolvedRequest, err := jsonutil.Marshal(r.BidRequest) if err != nil { resolvedRequest = json.RawMessage("{}") } @@ -1511,7 +1512,7 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, var userExtData []byte if userExt != nil { var err error - userExtData, err = json.Marshal(userExt) + userExtData, err = jsonutil.Marshal(userExt) if err != nil { return nil, err } @@ -1528,7 +1529,7 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, var regsExtData []byte if regsExt != nil { var err error - regsExtData, err = json.Marshal(regsExt) + regsExtData, err = jsonutil.Marshal(regsExt) if err != nil { return nil, err } @@ -1540,7 +1541,7 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, Ext: regsExtData, } } - return json.Marshal(bidRequest) + return jsonutil.Marshal(bidRequest) } func TestSetEffectiveAmpPubID(t *testing.T) { @@ -2191,7 +2192,7 @@ func TestValidAmpResponseWhenRequestRejected(t *testing.T) { assert.NoError(t, err, "Failed to read test file.") test := testCase{} - assert.NoError(t, json.Unmarshal(fileData, &test), "Failed to parse test file.") + assert.NoError(t, jsonutil.UnmarshalValid(fileData, &test), "Failed to parse test file.") request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?%s", test.Query), nil) recorder := httptest.NewRecorder() @@ -2211,8 +2212,8 @@ func TestValidAmpResponseWhenRequestRejected(t *testing.T) { var actualAmpResp AmpResponse var expectedAmpResp AmpResponse - assert.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &actualAmpResp), "Unable to unmarshal actual AmpResponse.") - assert.NoError(t, json.Unmarshal(test.ExpectedAmpResponse, &expectedAmpResp), "Unable to unmarshal expected AmpResponse.") + assert.NoError(t, jsonutil.UnmarshalValid(recorder.Body.Bytes(), &actualAmpResp), "Unable to unmarshal actual AmpResponse.") + assert.NoError(t, jsonutil.UnmarshalValid(test.ExpectedAmpResponse, &expectedAmpResp), "Unable to unmarshal expected AmpResponse.") // validate modules data separately, because it has dynamic data if expectedAmpResp.ORTB2.Ext.Prebid == nil { @@ -2248,7 +2249,7 @@ func TestSendAmpResponse_LogsErrors(t *testing.T) { { description: "Error logged when bid.ext unmarshal fails", expectedErrors: []error{ - errors.New("Critical error while unpacking AMP targets: unexpected end of JSON input"), + errors.New("Critical error while unpacking AMP targets: expect { or n, but found \""), }, expectedStatus: http.StatusInternalServerError, writer: httptest.NewRecorder(), @@ -2325,7 +2326,7 @@ func TestSendAmpResponse_LogsErrors(t *testing.T) { _, ao = sendAmpResponse(test.writer, test.hookExecutor, &exchange.AuctionResponse{BidResponse: test.response}, &reqWrapper, account, labels, ao, nil) - assert.Equal(t, ao.Errors, test.expectedErrors, "Invalid errors.") + assert.Equal(t, test.expectedErrors, ao.Errors, "Invalid errors.") assert.Equal(t, test.expectedStatus, ao.Status, "Invalid HTTP response status.") }) } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 0e1a23583a1..2cad7a2463a 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -52,6 +52,7 @@ import ( "github.com/prebid/prebid-server/usersync" "github.com/prebid/prebid-server/util/httputil" "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/uuidutil" "github.com/prebid/prebid-server/version" ) @@ -308,11 +309,11 @@ func setSeatNonBidRaw(request *openrtb_ext.RequestWrapper, auctionResponse *exch // by HoldAuction response := auctionResponse.BidResponse respExt := &openrtb_ext.ExtBidResponse{} - if err := json.Unmarshal(response.Ext, &respExt); err != nil { + if err := jsonutil.Unmarshal(response.Ext, &respExt); err != nil { return err } if setSeatNonBid(respExt, request, auctionResponse) { - if respExtJson, err := json.Marshal(respExt); err == nil { + if respExtJson, err := jsonutil.Marshal(respExt); err == nil { response.Ext = respExtJson return nil } else { @@ -445,7 +446,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric requestJson, rejectErr := hookExecutor.ExecuteEntrypointStage(httpRequest, requestJson) if rejectErr != nil { errs = []error{rejectErr} - if err = json.Unmarshal(requestJson, req.BidRequest); err != nil { + if err = jsonutil.UnmarshalValid(requestJson, req.BidRequest); err != nil { glog.Errorf("Failed to unmarshal BidRequest during entrypoint rejection: %s", err) } return @@ -493,7 +494,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric requestJson, rejectErr = hookExecutor.ExecuteRawAuctionStage(requestJson) if rejectErr != nil { errs = []error{rejectErr} - if err = json.Unmarshal(requestJson, req.BidRequest); err != nil { + if err = jsonutil.UnmarshalValid(requestJson, req.BidRequest); err != nil { glog.Errorf("Failed to unmarshal BidRequest during raw auction stage rejection: %s", err) } return @@ -522,7 +523,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric return nil, nil, nil, nil, nil, nil, errs } - if err := json.Unmarshal(requestJson, req.BidRequest); err != nil { + if err := jsonutil.UnmarshalValid(requestJson, req.BidRequest); err != nil { errs = []error{err} return } @@ -621,7 +622,7 @@ func mergeBidderParams(req *openrtb_ext.RequestWrapper) error { } bidderParams := map[string]map[string]json.RawMessage{} - if err := json.Unmarshal(bidderParamsJson, &bidderParams); err != nil { + if err := jsonutil.Unmarshal(bidderParamsJson, &bidderParams); err != nil { return nil } @@ -664,7 +665,7 @@ func mergeBidderParamsImpExt(impExt *openrtb_ext.ImpExt, reqExtParams map[string impExtBidderMap := map[string]json.RawMessage{} if len(impExtBidder) > 0 { - if err := json.Unmarshal(impExtBidder, &impExtBidderMap); err != nil { + if err := jsonutil.Unmarshal(impExtBidder, &impExtBidderMap); err != nil { continue } } @@ -678,7 +679,7 @@ func mergeBidderParamsImpExt(impExt *openrtb_ext.ImpExt, reqExtParams map[string } if modified { - impExtBidderJson, err := json.Marshal(impExtBidderMap) + impExtBidderJson, err := jsonutil.Marshal(impExtBidderMap) if err != nil { return fmt.Errorf("error marshalling ext.BIDDER: %s", err.Error()) } @@ -712,7 +713,7 @@ func mergeBidderParamsImpExtPrebid(impExt *openrtb_ext.ImpExt, reqExtParams map[ impExtPrebidBidderMap := map[string]json.RawMessage{} if len(impExtPrebidBidder) > 0 { - if err := json.Unmarshal(impExtPrebidBidder, &impExtPrebidBidderMap); err != nil { + if err := jsonutil.Unmarshal(impExtPrebidBidder, &impExtPrebidBidderMap); err != nil { continue } } @@ -726,7 +727,7 @@ func mergeBidderParamsImpExtPrebid(impExt *openrtb_ext.ImpExt, reqExtParams map[ } if modified { - impExtPrebidBidderJson, err := json.Marshal(impExtPrebidBidderMap) + impExtPrebidBidderJson, err := jsonutil.Marshal(impExtPrebidBidderMap) if err != nil { return fmt.Errorf("error marshalling ext.prebid.bidder.BIDDER: %s", err.Error()) } @@ -1195,7 +1196,7 @@ func fillAndValidateNative(n *openrtb2.Native, impIndex int) error { return fmt.Errorf("request.imp[%d].native missing required property \"request\"", impIndex) } var nativePayload nativeRequests.Request - if err := json.Unmarshal(json.RawMessage(n.Request), &nativePayload); err != nil { + if err := jsonutil.UnmarshalValid(json.RawMessage(n.Request), &nativePayload); err != nil { return err } @@ -1212,7 +1213,7 @@ func fillAndValidateNative(n *openrtb2.Native, impIndex int) error { return err } - serialized, err := json.Marshal(nativePayload) + serialized, err := jsonutil.Marshal(nativePayload) if err != nil { return err } @@ -2086,7 +2087,7 @@ func getJsonSyntaxError(testJSON []byte) (bool, string) { } type jNode map[string]*JsonNode docErrdoc := &jNode{} - docErr := json.Unmarshal(testJSON, docErrdoc) + docErr := jsonutil.UnmarshalValid(testJSON, docErrdoc) if uerror, ok := docErr.(*json.SyntaxError); ok { err := fmt.Sprintf("%s at offset %v", uerror.Error(), uerror.Offset) return true, err @@ -2235,7 +2236,7 @@ func (deps *endpointDeps) processStoredRequests(requestJson []byte, impInfo []Im } } if len(resolvedImps) > 0 { - newImpJson, err := json.Marshal(resolvedImps) + newImpJson, err := jsonutil.Marshal(resolvedImps) if err != nil { return nil, nil, []error{err} } @@ -2255,7 +2256,7 @@ func parseImpInfo(requestJson []byte) (impData []ImpExtPrebidData, errs []error) impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid") var impExtPrebid openrtb_ext.ExtImpPrebid if impExtData != nil { - if err := json.Unmarshal(impExtData, &impExtPrebid); err != nil { + if err := jsonutil.Unmarshal(impExtData, &impExtPrebid); err != nil { errs = append(errs, err) } } @@ -2384,7 +2385,7 @@ func getAccountID(pub *openrtb2.Publisher) string { if pub != nil { if pub.Ext != nil { var pubExt openrtb_ext.ExtPublisher - err := json.Unmarshal(pub.Ext, &pubExt) + err := jsonutil.Unmarshal(pub.Ext, &pubExt) if err == nil && pubExt.Prebid != nil && pubExt.Prebid.ParentAccount != nil && *pubExt.Prebid.ParentAccount != "" { return *pubExt.Prebid.ParentAccount } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 8204a80e3db..530d197e2ae 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -36,6 +36,7 @@ import ( "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/stored_responses" "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -192,7 +193,7 @@ func runEndToEndTest(t *testing.T, auctionEndpointHandler httprouter.Handle, tes // Either assert bid response or expected error if len(test.ExpectedErrorMessage) > 0 { - assert.True(t, strings.HasPrefix(actualJsonBidResponse, test.ExpectedErrorMessage), "Actual: %s \nExpected: %s. Filename: %s \n", actualJsonBidResponse, test.ExpectedErrorMessage, testFile) + assert.Contains(t, actualJsonBidResponse, test.ExpectedErrorMessage, "Actual: %s \nExpected: %s. Filename: %s \n", actualJsonBidResponse, test.ExpectedErrorMessage, testFile) } if len(test.ExpectedBidResponse) > 0 { @@ -200,9 +201,9 @@ func runEndToEndTest(t *testing.T, auctionEndpointHandler httprouter.Handle, tes var actualBidResponse openrtb2.BidResponse var err error - err = json.Unmarshal(test.ExpectedBidResponse, &expectedBidResponse) + err = jsonutil.Unmarshal(test.ExpectedBidResponse, &expectedBidResponse) if assert.NoError(t, err, "Could not unmarshal expected bidResponse taken from test file.\n Test file: %s\n Error:%s\n", testFile, err) { - err = json.Unmarshal([]byte(actualJsonBidResponse), &actualBidResponse) + err = jsonutil.UnmarshalValid([]byte(actualJsonBidResponse), &actualBidResponse) if assert.NoError(t, err, "Could not unmarshal actual bidResponse from auction.\n Test file: %s\n Error:%s\n actualJsonBidResponse: %s", testFile, err, actualJsonBidResponse) { assertBidResponseEqual(t, testFile, expectedBidResponse, actualBidResponse) } @@ -222,13 +223,13 @@ func compareWarnings(t *testing.T, expectedBidResponseExt, actualBidResponseExt } var expectedWarn []openrtb_ext.ExtBidderMessage - err = json.Unmarshal(expectedWarnings, &expectedWarn) + err = jsonutil.UnmarshalValid(expectedWarnings, &expectedWarn) if err != nil { assert.Fail(t, "error unmarshalling expected warnings data from response extension") } var actualWarn []openrtb_ext.ExtBidderMessage - err = json.Unmarshal(actualWarnings, &actualWarn) + err = jsonutil.UnmarshalValid(actualWarnings, &actualWarn) if err != nil { assert.Fail(t, "error unmarshalling actual warnings data from response extension") } @@ -476,8 +477,8 @@ func TestExplicitUserId(t *testing.T) { // processes aliases before it processes stored imps. Changing that order // would probably cause this test to fail. func TestBadAliasRequests(t *testing.T) { - doBadAliasRequest(t, "sample-requests/invalid-stored/bad_stored_imp.json", "Invalid request: Invalid JSON in Default Request Settings: invalid character '\"' after object key:value pair at offset 51\n") - doBadAliasRequest(t, "sample-requests/invalid-stored/bad_incoming_imp.json", "Invalid request: Invalid JSON in Incoming Request: invalid character '\"' after object key:value pair at offset 230\n") + doBadAliasRequest(t, "sample-requests/invalid-stored/bad_stored_imp.json", "Invalid request: Invalid JSON Document\n") + doBadAliasRequest(t, "sample-requests/invalid-stored/bad_incoming_imp.json", "Invalid request: Invalid JSON Document\n") } // doBadAliasRequest() is a customized variation of doRequest(), above @@ -1907,7 +1908,7 @@ func TestValidateRequestExt(t *testing.T) { { description: "prebid cache - bids - wrong type", givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":true}}}`), - expectedErrors: []string{`json: cannot unmarshal bool into Go struct field ExtRequestPrebidCache.cache.bids of type openrtb_ext.ExtRequestPrebidCacheBids`}, + expectedErrors: []string{"cannot unmarshal openrtb_ext.ExtRequestPrebidCache.Bids: expect { or n, but found t"}, }, { description: "prebid cache - bids - provided", @@ -1921,7 +1922,7 @@ func TestValidateRequestExt(t *testing.T) { { description: "prebid cache - vastxml - wrong type", givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"vastxml":true}}}`), - expectedErrors: []string{`json: cannot unmarshal bool into Go struct field ExtRequestPrebidCache.cache.vastxml of type openrtb_ext.ExtRequestPrebidCacheVAST`}, + expectedErrors: []string{"cannot unmarshal openrtb_ext.ExtRequestPrebidCache.VastXML: expect { or n, but found t"}, }, { description: "prebid cache - vastxml - provided", @@ -2555,7 +2556,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) { newRequest, _, errList := deps.processStoredRequests(json.RawMessage(test.givenRawData), impInfo, storedRequests, storedImps, storedBidRequestId, hasStoredBidRequest) assert.Empty(t, errList, test.description) - if err := json.Unmarshal(newRequest, req); err != nil { + if err := jsonutil.UnmarshalValid(newRequest, req); err != nil { t.Errorf("processStoredRequests Error: %s", err.Error()) } if test.expectedCur != "" { @@ -3464,7 +3465,7 @@ func TestGetAccountID(t *testing.T) { ParentAccount: &testParentAccount, }, } - testPubExtJSON, err := json.Marshal(testPubExt) + testPubExtJSON, err := jsonutil.Marshal(testPubExt) assert.NoError(t, err) testCases := []struct { @@ -4196,7 +4197,7 @@ func TestParseRequestParseImpInfoError(t *testing.T) { assert.Nil(t, resReq, "Result request should be nil due to incorrect imp") assert.Nil(t, impExtInfoMap, "Impression info map should be nil due to incorrect imp") assert.Len(t, errL, 1, "One error should be returned") - assert.Contains(t, errL[0].Error(), "echovideoattrs of type bool", "Incorrect error message") + assert.Contains(t, errL[0].Error(), "cannot unmarshal openrtb_ext.Options.EchoVideoAttrs", "Incorrect error message") } func TestParseGzipedRequest(t *testing.T) { @@ -4878,20 +4879,20 @@ func TestParseRequestMergeBidderParams(t *testing.T) { assert.NoError(t, resReq.RebuildRequest()) var expIExt, iExt map[string]interface{} - err := json.Unmarshal(test.expectedImpExt, &expIExt) + err := jsonutil.UnmarshalValid(test.expectedImpExt, &expIExt) assert.Nil(t, err, "unmarshal() should return nil error") assert.NotNil(t, resReq.BidRequest.Imp[0].Ext, "imp[0].Ext should not be nil") - err = json.Unmarshal(resReq.BidRequest.Imp[0].Ext, &iExt) + err = jsonutil.UnmarshalValid(resReq.BidRequest.Imp[0].Ext, &iExt) assert.Nil(t, err, "unmarshal() should return nil error") assert.Equal(t, expIExt, iExt, "bidderparams in imp[].Ext should match") var eReqE, reqE map[string]interface{} - err = json.Unmarshal(test.expectedReqExt, &eReqE) + err = jsonutil.UnmarshalValid(test.expectedReqExt, &eReqE) assert.Nil(t, err, "unmarshal() should return nil error") - err = json.Unmarshal(resReq.BidRequest.Ext, &reqE) + err = jsonutil.UnmarshalValid(resReq.BidRequest.Ext, &reqE) assert.Nil(t, err, "unmarshal() should return nil error") assert.Equal(t, eReqE, reqE, "req.Ext should match") @@ -5753,11 +5754,11 @@ func TestValidResponseAfterExecutingStages(t *testing.T) { var actualExt openrtb_ext.ExtBidResponse var expectedExt openrtb_ext.ExtBidResponse - assert.NoError(t, json.Unmarshal(test.ExpectedBidResponse, &expectedResp), "Unable to unmarshal expected BidResponse.") - assert.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &actualResp), "Unable to unmarshal actual BidResponse.") + assert.NoError(t, jsonutil.UnmarshalValid(test.ExpectedBidResponse, &expectedResp), "Unable to unmarshal expected BidResponse.") + assert.NoError(t, jsonutil.UnmarshalValid(recorder.Body.Bytes(), &actualResp), "Unable to unmarshal actual BidResponse.") if expectedResp.Ext != nil { - assert.NoError(t, json.Unmarshal(expectedResp.Ext, &expectedExt), "Unable to unmarshal expected ExtBidResponse.") - assert.NoError(t, json.Unmarshal(actualResp.Ext, &actualExt), "Unable to unmarshal actual ExtBidResponse.") + assert.NoError(t, jsonutil.UnmarshalValid(expectedResp.Ext, &expectedExt), "Unable to unmarshal expected ExtBidResponse.") + assert.NoError(t, jsonutil.UnmarshalValid(actualResp.Ext, &actualExt), "Unable to unmarshal actual ExtBidResponse.") } assertBidResponseEqual(t, tc.file, expectedResp, actualResp) @@ -5959,7 +5960,7 @@ func getObject(t *testing.T, filename, key string) json.RawMessage { assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", filename, err) var obj json.RawMessage - err = json.Unmarshal(testBidRequest, &obj) + err = jsonutil.UnmarshalValid(testBidRequest, &obj) if err != nil { t.Fatalf("Failed to fetch object with key '%s' ... got error: %v", key, err) } diff --git a/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_1.json b/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_1.json index 812f0664fbb..cf699fea0c9 100644 --- a/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_1.json +++ b/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_1.json @@ -11,5 +11,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: Invalid JSON in Incoming Request: invalid character ']' looking for beginning of value at offset" + "expectedErrorMessage": "Invalid request: expect { or n, but found" } diff --git a/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_imp.json b/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_imp.json index e3960b17399..fb0dc866d10 100644 --- a/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_imp.json +++ b/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_imp.json @@ -37,5 +37,5 @@ "tmax": 500 }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: Invalid JSON in Imp[0] of Incoming Request: invalid character '\"' after object key:value pair at offset 132\n" + "expectedErrorMessage": "Invalid request: Invalid JSON Document" } diff --git a/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_imp.json b/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_imp.json index 0fed6c32adf..ea19855e75a 100644 --- a/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_imp.json +++ b/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_imp.json @@ -33,5 +33,5 @@ "tmax": 500 }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: imp.ext.prebid.storedrequest.id 7: Stored Imp has Invalid JSON" + "expectedErrorMessage": "Invalid request: Invalid JSON Document" } diff --git a/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_req.json b/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_req.json index 4e9d7f03352..235eb26179b 100644 --- a/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_req.json +++ b/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_req.json @@ -23,5 +23,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: ext.prebid.storedrequest.id refers to Stored Request 3 which contains Invalid JSON" + "expectedErrorMessage": "Invalid request: expect { or n, but found" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/interstital-bad-perc.json b/endpoints/openrtb2/sample-requests/invalid-whole/interstital-bad-perc.json index 302372b5e5d..e5031d4597d 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/interstital-bad-perc.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/interstital-bad-perc.json @@ -49,5 +49,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100\n" + "expectedErrorMessage": "Invalid request: request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json b/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json index 5aa7fd4dea1..8db15b82a75 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json @@ -39,5 +39,5 @@ ] }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: json: cannot unmarshal object into Go struct field ExtAppPrebid.source of type string" + "expectedErrorMessage": "Invalid request: cannot unmarshal openrtb_ext.ExtAppPrebid.Source: expects \" or n, but found {" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/malformed-bid-request.json b/endpoints/openrtb2/sample-requests/invalid-whole/malformed-bid-request.json index c6fd52304a7..482af257d62 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/malformed-bid-request.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/malformed-bid-request.json @@ -2,5 +2,5 @@ "description": "Malformed bid request throws an error", "mockBidRequest": "malformed", "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: invalid character 'm' looking for beginning of value\n" + "expectedErrorMessage": "Invalid request: expect { or n, but found m" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json index ab44e3e2428..7ab2631b701 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json @@ -42,5 +42,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: json: cannot unmarshal string into Go value of type map[string]json.RawMessage\n" + "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: expect { or n, but found " } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json index a26db8a5695..af04627c3a9 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json @@ -46,5 +46,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go value of type string\n" + "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: expects \" or n, but found 1" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json index c4646550dd2..b710d589ea5 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json @@ -41,5 +41,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go value of type string" + "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: expects \" or n, but found 2" } diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index 365f480eb4c..e8506813eef 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -38,6 +38,7 @@ import ( "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/uuidutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) @@ -925,7 +926,7 @@ func (s mockCurrencyRatesClient) handle(w http.ResponseWriter, req *http.Request s.data.DataAsOfRaw = "2018-09-12" // Marshal the response and http write it - currencyServerJsonResponse, err := json.Marshal(&s.data) + currencyServerJsonResponse, err := jsonutil.Marshal(&s.data) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -949,13 +950,13 @@ func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { // Unmarshal exit if error var openrtb2Request openrtb2.BidRequest - if err := json.Unmarshal(buf.Bytes(), &openrtb2Request); err != nil { + if err := jsonutil.UnmarshalValid(buf.Bytes(), &openrtb2Request); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } var openrtb2ImpExt map[string]json.RawMessage - if err := json.Unmarshal(openrtb2Request.Imp[0].Ext, &openrtb2ImpExt); err != nil { + if err := jsonutil.UnmarshalValid(openrtb2Request.Imp[0].Ext, &openrtb2ImpExt); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -986,7 +987,7 @@ func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { } // Marshal the response and http write it - serverJsonResponse, err := json.Marshal(&serverResponseObject) + serverJsonResponse, err := jsonutil.Marshal(&serverResponseObject) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -1016,7 +1017,7 @@ func (a mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *ada for _, imp := range request.Imp { requestCopy.Imp = []openrtb2.Imp{imp} - requestJSON, err := json.Marshal(request) + requestJSON, err := jsonutil.Marshal(request) if err != nil { errors = append(errors, err) continue @@ -1049,7 +1050,7 @@ func (a mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapter } var publisherResponse openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &publisherResponse); err != nil { + if err := jsonutil.UnmarshalValid(responseData.Body, &publisherResponse); err != nil { return nil, []error{err} } @@ -1113,7 +1114,7 @@ func parseTestData(fileData []byte, testFile string) (testCase, error) { jsonTestConfig, _, _, err = jsonparser.Get(fileData, "config") if err == nil { - if err = json.Unmarshal(jsonTestConfig, parsedTestData.Config); err != nil { + if err = jsonutil.UnmarshalValid(jsonTestConfig, parsedTestData.Config); err != nil { return parsedTestData, fmt.Errorf("Error unmarshaling root.config from file %s. Desc: %v.", testFile, err) } } diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 8faf15543d3..94455e0cb52 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -2,7 +2,6 @@ package openrtb2 import ( "context" - "encoding/json" "errors" "fmt" "io" @@ -36,6 +35,7 @@ import ( "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/usersync" "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/ptrutil" "github.com/prebid/prebid-server/util/uuidutil" "github.com/prebid/prebid-server/version" @@ -179,7 +179,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re resolvedRequest := requestJson if debugLog.DebugEnabledOrOverridden { debugLog.Data.Request = string(requestJson) - if headerBytes, err := json.Marshal(r.Header); err == nil { + if headerBytes, err := jsonutil.Marshal(r.Header); err == nil { debugLog.Data.Headers = string(headerBytes) } else { debugLog.Data.Headers = fmt.Sprintf("Unable to marshal headers data: %s", err.Error()) @@ -219,7 +219,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re var bidReq = &openrtb2.BidRequest{} if deps.defaultRequest { - if err := json.Unmarshal(deps.defReqJSON, bidReq); err != nil { + if err := jsonutil.UnmarshalValid(deps.defReqJSON, bidReq); err != nil { err = fmt.Errorf("Invalid JSON in Default Request Settings: %s", err) handleError(&labels, w, []error{err}, &vo, &debugLog) return @@ -374,8 +374,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re vo.VideoResponse = bidResp - resp, err := json.Marshal(bidResp) - //resp, err := json.Marshal(response) + resp, err := jsonutil.Marshal(bidResp) if err != nil { errL := []error{err} handleError(&labels, w, errL, &vo, &debugLog) @@ -384,7 +383,6 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re w.Header().Set("Content-Type", "application/json") w.Write(resp) - } func cleanupVideoBidRequest(videoReq *openrtb_ext.BidRequestVideo, podErrors []PodError) *openrtb_ext.BidRequestVideo { @@ -507,7 +505,7 @@ func (deps *endpointDeps) loadStoredImp(storedImpId string) (openrtb2.Imp, []err return impr, err } - if err := json.Unmarshal(imp[storedImpId], &impr); err != nil { + if err := jsonutil.UnmarshalValid(imp[storedImpId], &impr); err != nil { return impr, []error{err} } return impr, nil @@ -536,7 +534,7 @@ func buildVideoResponse(bidresponse *openrtb2.BidResponse, podErrors []PodError) anyBidsReturned = true var tempRespBidExt openrtb_ext.ExtBid - if err := json.Unmarshal(bid.Ext, &tempRespBidExt); err != nil { + if err := jsonutil.UnmarshalValid(bid.Ext, &tempRespBidExt); err != nil { return nil, err } if tempRespBidExt.Prebid.Targeting[formatTargetingKey(openrtb_ext.HbVastCacheKey, seatBid.Seat)] == "" { @@ -705,13 +703,13 @@ func createBidExtension(videoRequest *openrtb_ext.BidRequestVideo) ([]byte, erro } extReq := openrtb_ext.ExtRequest{Prebid: prebid} - return json.Marshal(extReq) + return jsonutil.Marshal(extReq) } func (deps *endpointDeps) parseVideoRequest(request []byte, headers http.Header) (req *openrtb_ext.BidRequestVideo, errs []error, podErrors []PodError) { req = &openrtb_ext.BidRequestVideo{} - if err := json.Unmarshal(request, &req); err != nil { + if err := jsonutil.UnmarshalValid(request, &req); err != nil { errs = []error{err} return } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 19af821f205..281c80b204d 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -23,6 +23,7 @@ import ( "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/ptrutil" "github.com/prebid/openrtb/v19/adcom1" @@ -47,7 +48,7 @@ func TestVideoEndpointImpressionsNumber(t *testing.T) { respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} - if err := json.Unmarshal(respBytes, resp); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil { t.Fatalf("Unable to unmarshal response.") } @@ -80,7 +81,7 @@ func TestVideoEndpointImpressionsDuration(t *testing.T) { } var extData openrtb_ext.ExtRequest - json.Unmarshal(ex.lastRequest.Ext, &extData) + jsonutil.UnmarshalValid(ex.lastRequest.Ext, &extData) assert.NotNil(t, extData.Prebid.Targeting.IncludeBidderKeys, "Request ext incorrect: IncludeBidderKeys should be true ") assert.True(t, *extData.Prebid.Targeting.IncludeBidderKeys, "Request ext incorrect: IncludeBidderKeys should be true ") @@ -135,7 +136,7 @@ func TestCreateBidExtension(t *testing.T) { resExt := &openrtb_ext.ExtRequest{} - if err := json.Unmarshal(res, &resExt); err != nil { + if err := jsonutil.UnmarshalValid(res, &resExt); err != nil { assert.Fail(t, "Unable to unmarshal bid extension") } assert.Equal(t, resExt.Prebid.Targeting.DurationRangeSec, durationRange, "Duration range seconds is incorrect") @@ -178,7 +179,7 @@ func TestVideoEndpointDebugQueryTrue(t *testing.T) { respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} - if err := json.Unmarshal(respBytes, resp); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil { t.Fatalf("Unable to unmarshal response.") } @@ -216,7 +217,7 @@ func TestVideoEndpointDebugQueryFalse(t *testing.T) { respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} - if err := json.Unmarshal(respBytes, resp); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil { t.Fatalf("Unable to unmarshal response.") } @@ -272,7 +273,7 @@ func TestVideoEndpointDebugNoAdPods(t *testing.T) { respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} - if err := json.Unmarshal(respBytes, resp); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil { t.Fatalf("Unable to unmarshal response.") } @@ -1091,7 +1092,7 @@ func TestCCPA(t *testing.T) { t.Fatalf("%s: The request never made it into the exchange.", test.description) } extRegs := &openrtb_ext.ExtRegs{} - if err := json.Unmarshal(ex.lastRequest.Regs.Ext, extRegs); err != nil { + if err := jsonutil.UnmarshalValid(ex.lastRequest.Regs.Ext, extRegs); err != nil { t.Fatalf("%s: Failed to unmarshal reg.ext in request to the exchange: %v", test.description, err) } if test.expectConsentString { @@ -1103,7 +1104,7 @@ func TestCCPA(t *testing.T) { // Validate HTTP Response responseBytes := httpResponseRecorder.Body.Bytes() response := &openrtb_ext.BidResponseVideo{} - if err := json.Unmarshal(responseBytes, response); err != nil { + if err := jsonutil.UnmarshalValid(responseBytes, response); err != nil { t.Fatalf("%s: Unable to unmarshal response.", test.description) } assert.Len(t, ex.lastRequest.Imp, 11, test.description+":imps") @@ -1125,12 +1126,12 @@ func TestVideoEndpointAppendBidderNames(t *testing.T) { } var extData openrtb_ext.ExtRequest - json.Unmarshal(ex.lastRequest.Ext, &extData) + jsonutil.UnmarshalValid(ex.lastRequest.Ext, &extData) assert.True(t, extData.Prebid.Targeting.AppendBidderNames, "Request ext incorrect: AppendBidderNames should be true ") respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} - if err := json.Unmarshal(respBytes, resp); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil { t.Fatalf("Unable to unmarshal response.") } diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 387de6bd05f..8b919c3d596 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -1307,14 +1307,14 @@ func TestSetUIDEndpointMetrics(t *testing.T) { cfgAccountRequired: true, expectedResponseCode: 400, expectedMetrics: func(m *metrics.MetricsEngineMock) { - m.On("RecordSetUid", metrics.SetUidBadRequest).Once() + m.On("RecordSetUid", metrics.SetUidAccountConfigMalformed).Once() }, expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "pubmatic", UID: "", - Errors: []error{errors.New("unexpected end of JSON input")}, + Errors: []error{errCookieSyncAccountConfigMalformed}, Success: false, } a.On("LogSetUIDObject", &expected).Once() diff --git a/endpoints/version.go b/endpoints/version.go index 00d894963e6..0a837fd8bc3 100644 --- a/endpoints/version.go +++ b/endpoints/version.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/golang/glog" + "github.com/prebid/prebid-server/util/jsonutil" ) const versionEndpointValueNotSet = "not-set" @@ -29,7 +30,7 @@ func prepareVersionEndpointResponse(version, revision string) (json.RawMessage, revision = versionEndpointValueNotSet } - return json.Marshal(struct { + return jsonutil.Marshal(struct { Revision string `json:"revision"` Version string `json:"version"` }{ diff --git a/errortypes/code.go b/errortypes/code.go index 5cbf3f860b4..1de5e648cef 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -15,6 +15,8 @@ const ( MalformedAcctErrorCode ModuleRejectionErrorCode TmaxTimeoutErrorCode + FailedToMarshalErrorCode + FailedToUnmarshalErrorCode ) // Defines numeric codes for well-known warnings. diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index 18b70634c48..d31c4166b06 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -217,3 +217,37 @@ func (err *Warning) Code() int { func (err *Warning) Severity() Severity { return SeverityWarning } + +// FailedToUnmarshal should be used to represent errors that occur when unmarshaling raw json. +type FailedToUnmarshal struct { + Message string +} + +func (err *FailedToUnmarshal) Error() string { + return err.Message +} + +func (err *FailedToUnmarshal) Code() int { + return FailedToUnmarshalErrorCode +} + +func (err *FailedToUnmarshal) Severity() Severity { + return SeverityFatal +} + +// FailedToMarshal should be used to represent errors that occur when marshaling to a byte slice. +type FailedToMarshal struct { + Message string +} + +func (err *FailedToMarshal) Error() string { + return err.Message +} + +func (err *FailedToMarshal) Code() int { + return FailedToMarshalErrorCode +} + +func (err *FailedToMarshal) Severity() Severity { + return SeverityFatal +} diff --git a/exchange/auction.go b/exchange/auction.go index b2d6eb571ca..abbf56ba799 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -2,7 +2,6 @@ package exchange import ( "context" - "encoding/json" "encoding/xml" "errors" "fmt" @@ -17,6 +16,7 @@ import ( "github.com/prebid/prebid-server/exchange/entities" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" + "github.com/prebid/prebid-server/util/jsonutil" ) const ( @@ -83,7 +83,7 @@ func (d *DebugLog) PutDebugLogError(cache prebid_cache_client.Client, timeout in d.CacheKey = rawUUID.String() } - data, err := json.Marshal(d.CacheString) + data, err := jsonutil.Marshal(d.CacheString) if err != nil { return err } @@ -244,7 +244,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } } if bids { - if jsonBytes, err := json.Marshal(topBid.Bid); err == nil { + if jsonBytes, err := jsonutil.Marshal(topBid.Bid); err == nil { jsonBytes, err = evTracking.modifyBidJSON(topBid, bidderName, jsonBytes) if err != nil { errs = append(errs, err) @@ -265,7 +265,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } if vast && topBid.BidType == openrtb_ext.BidTypeVideo { vastXML := makeVAST(topBid.Bid) - if jsonBytes, err := json.Marshal(vastXML); err == nil { + if jsonBytes, err := jsonutil.Marshal(vastXML); err == nil { if useCustomCacheKey { toCache = append(toCache, prebid_cache_client.Cacheable{ Type: prebid_cache_client.TypeXML, @@ -292,7 +292,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, if len(toCache) > 0 && debugLog != nil && debugLog.DebugEnabledOrOverridden { debugLog.CacheKey = hbCacheID debugLog.BuildCacheString() - if jsonBytes, err := json.Marshal(debugLog.CacheString); err == nil { + if jsonBytes, err := jsonutil.Marshal(debugLog.CacheString); err == nil { toCache = append(toCache, prebid_cache_client.Cacheable{ Type: debugLog.CacheType, Data: jsonBytes, diff --git a/exchange/auction_test.go b/exchange/auction_test.go index ad0f8d4a72c..199b2abba77 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -2,7 +2,6 @@ package exchange import ( "context" - "encoding/json" "encoding/xml" "fmt" "os" @@ -17,6 +16,7 @@ import ( "github.com/prebid/prebid-server/exchange/entities" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/ptrutil" "github.com/stretchr/testify/assert" @@ -179,7 +179,7 @@ func loadCacheSpec(filename string) (*cacheSpec, error) { } var spec cacheSpec - if err := json.Unmarshal(specData, &spec); err != nil { + if err := jsonutil.UnmarshalValid(specData, &spec); err != nil { return nil, fmt.Errorf("Failed to unmarshal JSON from file: %v", err) } @@ -286,18 +286,18 @@ func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) { for i, expectedCacheable := range specData.ExpectedCacheables { found := false var expectedData interface{} - if err := json.Unmarshal(expectedCacheable.Data, &expectedData); err != nil { + if err := jsonutil.UnmarshalValid(expectedCacheable.Data, &expectedData); err != nil { t.Fatalf("Failed to decode expectedCacheables[%d].value: %v", i, err) } if s, ok := expectedData.(string); ok && expectedCacheable.Type == prebid_cache_client.TypeJSON { // decode again if we have pre-encoded json string values - if err := json.Unmarshal([]byte(s), &expectedData); err != nil { + if err := jsonutil.UnmarshalValid([]byte(s), &expectedData); err != nil { t.Fatalf("Failed to re-decode expectedCacheables[%d].value :%v", i, err) } } for j, cachedItem := range cache.items { var actualData interface{} - if err := json.Unmarshal(cachedItem.Data, &actualData); err != nil { + if err := jsonutil.UnmarshalValid(cachedItem.Data, &actualData); err != nil { t.Fatalf("Failed to decode actual cache[%d].value: %s", j, err) } if assert.ObjectsAreEqual(expectedData, actualData) && diff --git a/exchange/bidder.go b/exchange/bidder.go index 40135787b25..677743801f3 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -33,6 +33,7 @@ import ( "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "golang.org/x/net/context/ctxhttp" ) @@ -287,7 +288,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde errs = append(errs, moreErrs...) if nativeMarkup != nil { - markup, err := json.Marshal(*nativeMarkup) + markup, err := jsonutil.Marshal(*nativeMarkup) if err != nil { errs = append(errs, err) } else { @@ -406,7 +407,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeResponse.Response, []error) { var errs []error var nativeMarkup *nativeResponse.Response - if err := json.Unmarshal(json.RawMessage(bid.AdM), &nativeMarkup); err != nil || len(nativeMarkup.Assets) == 0 { + if err := jsonutil.UnmarshalValid(json.RawMessage(bid.AdM), &nativeMarkup); err != nil || len(nativeMarkup.Assets) == 0 { // Some bidders are returning non-IAB compliant native markup. In this case Prebid server will not be able to add types. E.g Facebook return nil, errs } @@ -418,7 +419,7 @@ func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeRes } var nativePayload nativeRequests.Request - if err := json.Unmarshal(json.RawMessage((*nativeImp).Request), &nativePayload); err != nil { + if err := jsonutil.UnmarshalValid(json.RawMessage((*nativeImp).Request), &nativePayload); err != nil { errs = append(errs, err) } @@ -636,7 +637,7 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou } } } else if bidder.config.Debug.TimeoutNotification.Log { - reqJSON, err := json.Marshal(req) + reqJSON, err := jsonutil.Marshal(req) var msg string if err == nil { msg = fmt.Sprintf("TimeoutNotification: Failed to generate timeout request: error(%s), bidder request(%s)", errL[0].Error(), string(reqJSON)) diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 09c811ba9c6..b7203e33b17 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -31,6 +31,7 @@ import ( "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/version" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -856,7 +857,7 @@ func TestMultiCurrencies(t *testing.T) { mockedHTTPServer := httptest.NewServer(http.HandlerFunc( func(rw http.ResponseWriter, req *http.Request) { - b, err := json.Marshal(tc.rates) + b, err := jsonutil.Marshal(tc.rates) if err == nil { rw.WriteHeader(http.StatusOK) rw.Write(b) @@ -1177,7 +1178,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { mockedHTTPServer := httptest.NewServer(http.HandlerFunc( func(rw http.ResponseWriter, req *http.Request) { - b, err := json.Marshal(tc.rates) + b, err := jsonutil.Marshal(tc.rates) if err == nil { rw.WriteHeader(http.StatusOK) rw.Write(b) @@ -2344,7 +2345,7 @@ func (bidder *goodSingleBidderWithStoredBidResp) MakeRequests(request *openrtb2. func (bidder *goodSingleBidderWithStoredBidResp) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.UnmarshalValid(response.Body, &bidResp); err != nil { return nil, []error{err} } bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) diff --git a/exchange/events.go b/exchange/events.go index e4f6b0d503f..fb535f6f70e 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -1,7 +1,6 @@ package exchange import ( - "encoding/json" "time" "github.com/prebid/prebid-server/exchange/entities" @@ -11,6 +10,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/endpoints/events" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" ) // eventTracking has configuration fields needed for adding event tracking to an auction response @@ -91,7 +91,7 @@ func (ev *eventTracking) modifyBidJSON(pbsBid *entities.PbsOrtbBid, bidderName o winEventURL = ev.makeEventURL(analytics.Win, pbsBid, bidderName) } // wurl attribute is not in the schema, so we have to patch - patch, err := json.Marshal(map[string]string{"wurl": winEventURL}) + patch, err := jsonutil.Marshal(map[string]string{"wurl": winEventURL}) if err != nil { return jsonBytes, err } diff --git a/exchange/exchange.go b/exchange/exchange.go index a8c76a5ebb1..6ebde7dca9d 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -35,6 +35,7 @@ import ( "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_responses" "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/maputil" "github.com/buger/jsonparser" @@ -278,7 +279,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog if err := r.BidRequestWrapper.RebuildRequest(); err != nil { return nil, err } - resolvedBidReq, err := json.Marshal(r.BidRequestWrapper.BidRequest) + resolvedBidReq, err := jsonutil.Marshal(r.BidRequestWrapper.BidRequest) if err != nil { return nil, err } @@ -442,7 +443,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, *r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs) if debugLog.DebugEnabledOrOverridden { - if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { + if bidRespExtBytes, err := jsonutil.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) } else { debugLog.Data.Response = "Unable to marshal response ext for debugging" @@ -463,7 +464,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog if debugLog.DebugEnabledOrOverridden { - if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { + if bidRespExtBytes, err := jsonutil.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) } else { debugLog.Data.Response = "Unable to marshal response ext for debugging" @@ -1292,7 +1293,7 @@ func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impEx var extMap map[string]interface{} if len(ext) != 0 { - if err := json.Unmarshal(ext, &extMap); err != nil { + if err := jsonutil.Unmarshal(ext, &extMap); err != nil { return nil, err } } else { @@ -1316,7 +1317,7 @@ func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impEx Meta openrtb_ext.ExtBidPrebidMeta `json:"meta"` } `json:"prebid"` }{} - if err := json.Unmarshal(ext, &metaContainer); err != nil { + if err := jsonutil.Unmarshal(ext, &metaContainer); err != nil { return nil, fmt.Errorf("error validaing response from server, %s", err) } prebid.Meta = &metaContainer.Prebid.Meta @@ -1337,7 +1338,7 @@ func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impEx } } extMap[openrtb_ext.PrebidExtKey] = prebid - return json.Marshal(extMap) + return jsonutil.Marshal(extMap) } // If bid got cached inside `(a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb2.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string)`, @@ -1441,7 +1442,7 @@ func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessag for impId, storedResp := range storedAuctionResponses { var seatBids []openrtb2.SeatBid - if err := json.Unmarshal(storedResp, &seatBids); err != nil { + if err := jsonutil.UnmarshalValid(storedResp, &seatBids); err != nil { return nil, nil, nil, err } for _, seat := range seatBids { @@ -1460,7 +1461,7 @@ func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessag if seat.Ext != nil { var seatExt openrtb_ext.ExtBidResponse - if err := json.Unmarshal(seat.Ext, &seatExt); err != nil { + if err := jsonutil.Unmarshal(seat.Ext, &seatExt); err != nil { return nil, nil, nil, err } // add in FLEDGE response with impId substituted diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index e57900cdfee..ff989bad91d 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -41,6 +41,7 @@ import ( "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -379,7 +380,7 @@ func TestDebugBehaviour(t *testing.T) { assert.NotNilf(t, outBidResponse.Ext, "%s. outBidResponse.Ext should not be nil \n", test.desc) assert.False(t, auctionRequest.BidderResponseStartTime.IsZero()) actualExt := &openrtb_ext.ExtBidResponse{} - err = json.Unmarshal(outBidResponse.Ext, actualExt) + err = jsonutil.UnmarshalValid(outBidResponse.Ext, actualExt) assert.NoErrorf(t, err, "%s. \"ext\" JSON field could not be unmarshaled. err: \"%v\" \n outBidResponse.Ext: \"%s\" \n", test.desc, err, outBidResponse.Ext) assert.NotEmpty(t, actualExt.Prebid, "%s. ext.prebid should not be empty") @@ -541,7 +542,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { assert.False(t, auctionRequest.BidderResponseStartTime.IsZero()) actualExt := &openrtb_ext.ExtBidResponse{} - err = json.Unmarshal(outBidResponse.Ext, actualExt) + err = jsonutil.UnmarshalValid(outBidResponse.Ext, actualExt) assert.NoErrorf(t, err, "JSON field unmarshaling err. ") assert.NotEmpty(t, actualExt.Prebid, "ext.prebid should not be empty") @@ -1143,7 +1144,7 @@ func TestGetAuctionCurrencyRates(t *testing.T) { for _, tc := range testCases { // Test setup: - jsonPbsRates, err := json.Marshal(tc.given.pbsRates) + jsonPbsRates, err := jsonutil.Marshal(tc.given.pbsRates) if err != nil { t.Fatalf("Failed to marshal PBS rates: %v", err) } @@ -1627,7 +1628,7 @@ func TestBidReturnsCreative(t *testing.T) { assert.Equal(t, test.expectedCreativeMarkup, resultingBids[0].AdM, "%s. Ad markup string doesn't match expected \n", test.description) var bidExt openrtb_ext.ExtBid - json.Unmarshal(resultingBids[0].Ext, &bidExt) + jsonutil.UnmarshalValid(resultingBids[0].Ext, &bidExt) assert.Equal(t, 0, bidExt.Prebid.DealPriority, "%s. Test should have DealPriority set to 0", test.description) assert.Equal(t, false, bidExt.Prebid.DealTierSatisfied, "%s. Test should have DealTierSatisfied set to false", test.description) } @@ -1966,7 +1967,7 @@ func TestBidResponseImpExtInfo(t *testing.T) { impExtInfo["some-impression-id"] = ImpExtInfo{ true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), - json.RawMessage(`{"imp_passthrough_val": 1}`)} + json.RawMessage(`{"imp_passthrough_val":1}`)} expectedBidResponseExt := `{"origbidcpm":0,"prebid":{"type":"video","passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}` @@ -2284,7 +2285,7 @@ func loadFile(filename string) (*exchangeSpec, error) { } var spec exchangeSpec - if err := json.Unmarshal(specData, &spec); err != nil { + if err := jsonutil.UnmarshalValid(specData, &spec); err != nil { return nil, fmt.Errorf("Failed to unmarshal JSON from file: %v", err) } @@ -2381,7 +2382,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { auctionRequest.Account.DefaultBidLimit = spec.MultiBid.AccountMaxBid requestExt := &openrtb_ext.ExtRequest{} - err := json.Unmarshal(spec.IncomingRequest.OrtbRequest.Ext, requestExt) + err := jsonutil.UnmarshalValid(spec.IncomingRequest.OrtbRequest.Ext, requestExt) assert.NoError(t, err, "invalid request ext") validatedMultiBids, errs := openrtb_ext.ValidateAndBuildExtMultiBid(&requestExt.Prebid) for _, err := range errs { // same as in validateRequestExt(). @@ -2392,7 +2393,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { } requestExt.Prebid.MultiBid = validatedMultiBids - updateReqExt, err := json.Marshal(requestExt) + updateReqExt, err := jsonutil.Marshal(requestExt) assert.NoError(t, err, "invalid request ext") auctionRequest.BidRequestWrapper.Ext = updateReqExt } @@ -2455,7 +2456,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { actualPassthrough := "" actualBidRespExt := &openrtb_ext.ExtBidResponse{} if bid.Ext != nil { - if err := json.Unmarshal(bid.Ext, actualBidRespExt); err != nil { + if err := jsonutil.UnmarshalValid(bid.Ext, actualBidRespExt); err != nil { assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) } if actualBidRespExt.Prebid != nil { @@ -2464,7 +2465,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { } expectedBidRespExt := &openrtb_ext.ExtBidResponse{} if spec.Response.Ext != nil { - if err := json.Unmarshal(spec.Response.Ext, expectedBidRespExt); err != nil { + if err := jsonutil.UnmarshalValid(spec.Response.Ext, expectedBidRespExt); err != nil { assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) } if expectedBidRespExt.Prebid != nil { @@ -2495,11 +2496,11 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { actualBidRespExt := &openrtb_ext.ExtBidResponse{} expectedBidRespExt := &openrtb_ext.ExtBidResponse{} if bid.Ext != nil { - if err := json.Unmarshal(bid.Ext, actualBidRespExt); err != nil { + if err := jsonutil.UnmarshalValid(bid.Ext, actualBidRespExt); err != nil { assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) } } - if err := json.Unmarshal(spec.Response.Ext, expectedBidRespExt); err != nil { + if err := jsonutil.UnmarshalValid(spec.Response.Ext, expectedBidRespExt); err != nil { assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) } @@ -2529,7 +2530,7 @@ func extractResponseTimes(t *testing.T, context string, bid *openrtb2.BidRespons return nil } else { responseTimes := make(map[string]int) - if err := json.Unmarshal(data, &responseTimes); err != nil { + if err := jsonutil.UnmarshalValid(data, &responseTimes); err != nil { t.Errorf("%s: Failed to unmarshal ext.responsetimemillis into map[string]int: %v", context, err) return nil } @@ -4180,37 +4181,29 @@ func TestMakeBidExtJSON(t *testing.T) { description: "Invalid extension, valid extBidPrebid and valid imp ext info", ext: json.RawMessage(`{invalid json}`), extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, - impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`"prebid": {"passthrough": {"imp_passthrough_val": some_val}}"`)}}, expectedBidExt: ``, - expectedErrMessage: "invalid character", - }, - { - description: "Valid extension, empty extBidPrebid and invalid imp ext info", - ext: json.RawMessage(`{"video":{"h":100}}`), - extBidPrebid: openrtb_ext.ExtBidPrebid{}, - impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{!}}`), nil}}, - expectedBidExt: ``, - expectedErrMessage: "invalid character", + expectedErrMessage: "expects \" or n, but found i", }, { description: "Meta - Invalid", ext: json.RawMessage(`{"prebid":{"meta":{"brandId":"foo"}}}`), // brandId should be an int, but is a string in this test case extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner")}, impExtInfo: nil, - expectedErrMessage: "error validaing response from server, json: cannot unmarshal string into Go struct field ExtBidPrebidMeta.prebid.meta.brandId of type int", + expectedErrMessage: "error validaing response from server, cannot unmarshal openrtb_ext.ExtBidPrebidMeta.BrandID: unexpected character: \xff", }, - // add invalid } for _, test := range testCases { - result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id", test.origbidcpm, test.origbidcur) + t.Run(test.description, func(t *testing.T) { + result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id", test.origbidcpm, test.origbidcur) - if test.expectedErrMessage == "" { - assert.JSONEq(t, test.expectedBidExt, string(result), "Incorrect result") - assert.NoError(t, err, "Error should not be returned") - } else { - assert.Contains(t, err.Error(), test.expectedErrMessage, "incorrect error message") - } + if test.expectedErrMessage == "" { + assert.JSONEq(t, test.expectedBidExt, string(result), "Incorrect result") + assert.NoError(t, err, "Error should not be returned") + } else { + assert.Contains(t, err.Error(), test.expectedErrMessage, "incorrect error message") + } + }) } } @@ -5343,12 +5336,12 @@ func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest B func diffOrtbRequests(t *testing.T, description string, expected *openrtb2.BidRequest, actual *openrtb2.BidRequest) { t.Helper() - actualJSON, err := json.Marshal(actual) + actualJSON, err := jsonutil.Marshal(actual) if err != nil { t.Fatalf("%s failed to marshal actual BidRequest into JSON. %v", description, err) } - expectedJSON, err := json.Marshal(expected) + expectedJSON, err := jsonutil.Marshal(expected) if err != nil { t.Fatalf("%s failed to marshal expected BidRequest into JSON. %v", description, err) } @@ -5366,12 +5359,12 @@ func diffOrtbResponses(t *testing.T, description string, expected *openrtb2.BidR // this implementation detail, I'm cutting a corner and ignoring it here. actualSeats := mapifySeatBids(t, description, actual.SeatBid) expectedSeats := mapifySeatBids(t, description, expected.SeatBid) - actualJSON, err := json.Marshal(actualSeats) + actualJSON, err := jsonutil.Marshal(actualSeats) if err != nil { t.Fatalf("%s failed to marshal actual BidResponse into JSON. %v", description, err) } - expectedJSON, err := json.Marshal(expectedSeats) + expectedJSON, err := jsonutil.Marshal(expectedSeats) if err != nil { t.Fatalf("%s failed to marshal expected BidResponse into JSON. %v", description, err) } @@ -5501,13 +5494,13 @@ func getInfoFromImp(req *openrtb_ext.RequestWrapper) (json.RawMessage, string, e impID := imp.ID var bidderExts map[string]json.RawMessage - if err := json.Unmarshal(imp.Ext, &bidderExts); err != nil { + if err := jsonutil.UnmarshalValid(imp.Ext, &bidderExts); err != nil { return nil, "", err } var extPrebid openrtb_ext.ExtImpPrebid if bidderExts[openrtb_ext.PrebidExtKey] != nil { - if err := json.Unmarshal(bidderExts[openrtb_ext.PrebidExtKey], &extPrebid); err != nil { + if err := jsonutil.UnmarshalValid(bidderExts[openrtb_ext.PrebidExtKey], &extPrebid); err != nil { return nil, "", err } } diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index a5f49689349..25c6ea1714a 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -16,6 +16,7 @@ import ( "github.com/prebid/prebid-server/hooks/hookexecution" metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/ptrutil" "github.com/prebid/openrtb/v19/openrtb2" @@ -182,7 +183,7 @@ func buildParams(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb2.B paramsPrebid["bidder"] = paramsPrebidBidders params["prebid"] = paramsPrebid - ext, err := json.Marshal(params) + ext, err := jsonutil.Marshal(params) if err != nil { t.Fatalf("Failed to make imp exts: %v", err) } @@ -224,7 +225,7 @@ func buildBidMap(seatBids []openrtb2.SeatBid, numBids int) map[string]*openrtb2. func parseTargets(t *testing.T, bid *openrtb2.Bid) map[string]string { t.Helper() var parsed openrtb_ext.ExtBid - if err := json.Unmarshal(bid.Ext, &parsed); err != nil { + if err := jsonutil.UnmarshalValid(bid.Ext, &parsed); err != nil { t.Fatalf("Unexpected error parsing targeting params: %v", err) } return parsed.Prebid.Targeting diff --git a/exchange/utils.go b/exchange/utils.go index 70988fbf7b9..c7acfaefce3 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -26,6 +26,7 @@ import ( "github.com/prebid/prebid-server/privacy/lmt" "github.com/prebid/prebid-server/schain" "github.com/prebid/prebid-server/stored_responses" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/ptrutil" ) @@ -282,7 +283,7 @@ func ExtractReqExtBidderParamsMap(bidRequest *openrtb2.BidRequest) (map[string]j reqExt := &openrtb_ext.ExtRequest{} if len(bidRequest.Ext) > 0 { - err := json.Unmarshal(bidRequest.Ext, &reqExt) + err := jsonutil.Unmarshal(bidRequest.Ext, &reqExt) if err != nil { return nil, fmt.Errorf("error decoding Request.ext : %s", err.Error()) } @@ -293,7 +294,7 @@ func ExtractReqExtBidderParamsMap(bidRequest *openrtb2.BidRequest) (map[string]j } var bidderParams map[string]json.RawMessage - err := json.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams) + err := jsonutil.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams) if err != nil { return nil, err } @@ -413,7 +414,7 @@ func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, request } // Marshal New Prebid Object - prebidJson, err := json.Marshal(prebid) + prebidJson, err := jsonutil.Marshal(prebid) if err != nil { return nil, err } @@ -421,7 +422,7 @@ func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, request // Parse Existing Ext extMap := make(map[string]json.RawMessage) if len(requestExt) != 0 { - if err := json.Unmarshal(requestExt, &extMap); err != nil { + if err := jsonutil.Unmarshal(requestExt, &extMap); err != nil { return nil, err } } @@ -434,7 +435,7 @@ func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, request } if len(extMap) > 0 { - return json.Marshal(extMap) + return jsonutil.Marshal(extMap) } else { return nil, nil } @@ -518,7 +519,7 @@ func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { } var userExt openrtb_ext.ExtUser - if err := json.Unmarshal(user.Ext, &userExt); err != nil { + if err := jsonutil.Unmarshal(user.Ext, &userExt); err != nil { return nil, err } if userExt.Prebid == nil { @@ -532,7 +533,7 @@ func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { // Remarshal (instead of removing) if the ext has other known fields if userExt.Consent != "" || len(userExt.Eids) > 0 { - if newUserExtBytes, err := json.Marshal(userExt); err != nil { + if newUserExtBytes, err := jsonutil.Marshal(userExt); err != nil { return nil, err } else { user.Ext = newUserExtBytes @@ -556,20 +557,20 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { for i, imp := range imps { var impExt map[string]json.RawMessage - if err := json.Unmarshal(imp.Ext, &impExt); err != nil { + if err := jsonutil.UnmarshalValid(imp.Ext, &impExt); err != nil { return nil, fmt.Errorf("invalid json for imp[%d]: %v", i, err) } var impExtPrebid map[string]json.RawMessage if impExtPrebidJSON, exists := impExt[openrtb_ext.PrebidExtKey]; exists { // validation already performed by impExt unmarshal. no error is possible here, proven by tests. - json.Unmarshal(impExtPrebidJSON, &impExtPrebid) + jsonutil.Unmarshal(impExtPrebidJSON, &impExtPrebid) } var impExtPrebidBidder map[string]json.RawMessage if impExtPrebidBidderJSON, exists := impExtPrebid[openrtb_ext.PrebidExtBidderKey]; exists { // validation already performed by impExt unmarshal. no error is possible here, proven by tests. - json.Unmarshal(impExtPrebidBidderJSON, &impExtPrebidBidder) + jsonutil.Unmarshal(impExtPrebidBidderJSON, &impExtPrebidBidder) } sanitizedImpExt, err := createSanitizedImpExt(impExt, impExtPrebid) @@ -582,7 +583,7 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { sanitizedImpExt[openrtb_ext.PrebidExtBidderKey] = bidderExt - impExtJSON, err := json.Marshal(sanitizedImpExt) + impExtJSON, err := jsonutil.Marshal(sanitizedImpExt) if err != nil { return nil, fmt.Errorf("unable to remove other bidder fields for imp[%d]: cannot marshal ext: %v", i, err) } @@ -622,7 +623,7 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map // marshal sanitized imp[].ext.prebid if len(sanitizedImpPrebidExt) > 0 { - if impExtPrebidJSON, err := json.Marshal(sanitizedImpPrebidExt); err == nil { + if impExtPrebidJSON, err := jsonutil.Marshal(sanitizedImpPrebidExt); err == nil { sanitizedImpExt[openrtb_ext.PrebidExtKey] = impExtPrebidJSON } else { return nil, fmt.Errorf("cannot marshal ext.prebid: %v", err) @@ -686,7 +687,7 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque // low level unmarshal to preserve other request.user.ext values. prebid server is non-destructive. var userExt map[string]json.RawMessage - if err := json.Unmarshal(request.User.Ext, &userExt); err != nil { + if err := jsonutil.Unmarshal(request.User.Ext, &userExt); err != nil { return err } @@ -696,7 +697,7 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque } var eids []openrtb2.EID - if err := json.Unmarshal(eidsJSON, &eids); err != nil { + if err := jsonutil.Unmarshal(eidsJSON, &eids); err != nil { return err } @@ -739,7 +740,7 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque if len(eidsAllowed) == 0 { delete(userExt, "eids") } else { - eidsRaw, err := json.Marshal(eidsAllowed) + eidsRaw, err := jsonutil.Marshal(eidsAllowed) if err != nil { return err } @@ -752,7 +753,7 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque return nil } - userExtJSON, err := json.Marshal(userExt) + userExtJSON, err := jsonutil.Marshal(userExt) if err != nil { return err } @@ -781,7 +782,7 @@ func resolveBidder(bidder string, requestAliases map[string]string) (openrtb_ext func parseAliases(orig *openrtb2.BidRequest) (map[string]string, []error) { var aliases map[string]string if value, dataType, _, err := jsonparser.Get(orig.Ext, openrtb_ext.PrebidExtKey, "aliases"); dataType == jsonparser.Object && err == nil { - if err := json.Unmarshal(value, &aliases); err != nil { + if err := jsonutil.Unmarshal(value, &aliases); err != nil { return nil, []error{err} } } else if dataType != jsonparser.NotExist && err != jsonparser.KeyPathNotFoundError { @@ -794,7 +795,7 @@ func parseAliases(orig *openrtb2.BidRequest) (map[string]string, []error) { func parseAliasesGVLIDs(orig *openrtb2.BidRequest) (map[string]uint16, []error) { var aliasesGVLIDs map[string]uint16 if value, dataType, _, err := jsonparser.Get(orig.Ext, openrtb_ext.PrebidExtKey, "aliasgvlids"); dataType == jsonparser.Object && err == nil { - if err := json.Unmarshal(value, &aliasesGVLIDs); err != nil { + if err := jsonutil.Unmarshal(value, &aliasesGVLIDs); err != nil { return nil, []error{err} } } else if dataType != jsonparser.NotExist && err != jsonparser.KeyPathNotFoundError { @@ -1089,7 +1090,7 @@ func getPrebidMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { if bid.Ext != nil { var bidExt openrtb_ext.ExtBid - err = json.Unmarshal(bid.Ext, &bidExt) + err = jsonutil.Unmarshal(bid.Ext, &bidExt) if err == nil && bidExt.Prebid != nil { if bidType, err = openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)); err == nil { return bidType, nil diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 1be1aaa8493..b413a6b292e 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -18,6 +18,7 @@ import ( "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -187,21 +188,21 @@ func TestSplitImps(t *testing.T) { givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`malformed`)}, }, - expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", + expectedError: "invalid json for imp[0]: expect { or n, but found m", }, { description: "Malformed imp.ext.prebid", givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"prebid": malformed}`)}, }, - expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", + expectedError: "invalid json for imp[0]: do not know how to skip: 109", }, { description: "Malformed imp.ext.prebid.bidder", givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"prebid": {"bidder": malformed}}`)}, }, - expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", + expectedError: "invalid json for imp[0]: do not know how to skip: 109", }, } @@ -403,22 +404,6 @@ func TestCreateSanitizedImpExt(t *testing.T) { }, expectedError: "", }, - { - description: "Marshal Error - imp.ext.prebid", - givenImpExt: map[string]json.RawMessage{ - "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), - }, - givenImpExtPrebid: map[string]json.RawMessage{ - "options": json.RawMessage(`malformed`), // String value without quotes. - }, - expected: nil, - expectedError: "cannot marshal ext.prebid: json: error calling MarshalJSON for type json.RawMessage: invalid character 'm' looking for beginning of value", - }, } for _, test := range testCases { @@ -592,7 +577,7 @@ func TestExtractAdapterReqBidderParamsMap(t *testing.T) { name: "malformed req.ext", givenBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage("malformed")}, want: nil, - wantErr: errors.New("error decoding Request.ext : invalid character 'm' looking for beginning of value"), + wantErr: errors.New("error decoding Request.ext : expect { or n, but found m"), }, { name: "extract bidder params from req.Ext for input request in adapter code", @@ -1081,7 +1066,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { req.Regs = &openrtb2.Regs{Ext: test.reqRegsExt} var reqExtStruct openrtb_ext.ExtRequest - err := json.Unmarshal(req.Ext, &reqExtStruct) + err := jsonutil.UnmarshalValid(req.Ext, &reqExtStruct) assert.NoError(t, err, test.description+":marshal_ext") auctionReq := AuctionRequest{ @@ -1240,7 +1225,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { if test.inExt != nil { req.Ext = test.inExt extRequest = &openrtb_ext.ExtRequest{} - err := json.Unmarshal(req.Ext, extRequest) + err := jsonutil.UnmarshalValid(req.Ext, extRequest) assert.NoErrorf(t, err, test.description+":Error unmarshaling inExt") } @@ -1311,7 +1296,7 @@ func TestCleanOpenRTBRequestsBidderParams(t *testing.T) { if test.inExt != nil { req.Ext = test.inExt extRequest = &openrtb_ext.ExtRequest{} - err := json.Unmarshal(req.Ext, extRequest) + err := jsonutil.UnmarshalValid(req.Ext, extRequest) assert.NoErrorf(t, err, test.description+":Error unmarshaling inExt") } @@ -2492,7 +2477,7 @@ func TestBuildRequestExtForBidder(t *testing.T) { for _, test := range testCases { requestExtParsed := &openrtb_ext.ExtRequest{} if test.requestExt != nil { - err := json.Unmarshal(test.requestExt, requestExtParsed) + err := jsonutil.UnmarshalValid(test.requestExt, requestExtParsed) if !assert.NoError(t, err, test.description+":parse_ext") { continue } @@ -2533,7 +2518,7 @@ func TestBuildRequestExtForBidder_RequestExtMalformed(t *testing.T) { actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes) assert.Equal(t, json.RawMessage(nil), actualJson) - assert.EqualError(t, actualErr, "invalid character 'm' looking for beginning of value") + assert.EqualError(t, actualErr, "expect { or n, but found m") } // newAdapterAliasBidRequest builds a BidRequest with aliases @@ -2864,17 +2849,17 @@ func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) { { description: "Malformed Ext", userExt: json.RawMessage(`malformed`), - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErr: "expect { or n, but found m", }, { description: "Malformed Eid Array Type", userExt: json.RawMessage(`{"eids":[42]}`), - expectedErr: "json: cannot unmarshal number into Go value of type openrtb2.EID", + expectedErr: "cannot unmarshal []openrtb2.EID: expect { or n, but found 4", }, { description: "Malformed Eid Item Type", userExt: json.RawMessage(`{"eids":[{"source":42,"id":"anyID"}]}`), - expectedErr: "json: cannot unmarshal number into Go struct field EID.source of type string", + expectedErr: "cannot unmarshal openrtb2.EID.Source: expects \" or n, but found 4", }, } @@ -3088,7 +3073,7 @@ func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { } extRequest := &openrtb_ext.ExtRequest{} - err := json.Unmarshal(req.Ext, extRequest) + err := jsonutil.UnmarshalValid(req.Ext, extRequest) assert.NoErrorf(t, err, "Error unmarshaling inExt") auctionReq := AuctionRequest{ @@ -3172,7 +3157,7 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { H: 600, }}, }, - Ext: json.RawMessage(`{"bidder":{"placementId":1}}`), + Ext: json.RawMessage(`{"bidder":{"placementId": 1}}`), }}, }, { @@ -3200,7 +3185,7 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { H: 600, }}, }, - Ext: json.RawMessage(`{"bidder":{"placementId":1}}`), + Ext: json.RawMessage(`{"bidder":{"placementId": 1}}`), }}, }, } @@ -3635,7 +3620,7 @@ func TestCleanOpenRTBRequestsFilterBidderRequestExt(t *testing.T) { if test.inExt != nil { req.Ext = test.inExt extRequest = &openrtb_ext.ExtRequest{} - err := json.Unmarshal(req.Ext, extRequest) + err := jsonutil.UnmarshalValid(req.Ext, extRequest) assert.NoErrorf(t, err, test.desc+":Error unmarshaling inExt") } @@ -4396,7 +4381,7 @@ func TestGetPrebidMediaTypeForBid(t *testing.T) { { description: "Invalid bid ext", inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", Ext: json.RawMessage(`[true`)}, - expectedError: "Failed to parse bid mediatype for impression \"impId\", unexpected end of JSON input", + expectedError: "Failed to parse bid mediatype for impression \"impId\", expect { or n, but found [", }, { description: "Bid ext is nil", @@ -4437,7 +4422,7 @@ func TestGetMediaTypeForBid(t *testing.T) { { description: "invalid bid ext", inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", Ext: json.RawMessage(`{"prebid"`)}, - expectedError: "Failed to parse bid mediatype for impression \"impId\", unexpected end of JSON input", + expectedError: "Failed to parse bid mediatype for impression \"impId\", expect :, but found \x00", }, { description: "Valid bid ext with mtype native", diff --git a/firstpartydata/first_party_data.go b/firstpartydata/first_party_data.go index b63feb1ec52..b23ba576fcf 100644 --- a/firstpartydata/first_party_data.go +++ b/firstpartydata/first_party_data.go @@ -10,6 +10,7 @@ import ( "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/ortb" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/ptrutil" ) @@ -215,7 +216,7 @@ func mergeUser(v *openrtb2.User, overrideJSON json.RawMessage) error { } // Merge - if err := json.Unmarshal(overrideJSON, &v); err != nil { + if err := jsonutil.Unmarshal(overrideJSON, &v); err != nil { return err } @@ -309,7 +310,7 @@ func mergeSite(v *openrtb2.Site, overrideJSON json.RawMessage, bidderName string } // Merge - if err := json.Unmarshal(overrideJSON, &v); err != nil { + if err := jsonutil.Unmarshal(overrideJSON, &v); err != nil { return err } @@ -426,7 +427,7 @@ func mergeApp(v *openrtb2.App, overrideJSON json.RawMessage) error { } // Merge - if err := json.Unmarshal(overrideJSON, &v); err != nil { + if err := jsonutil.Unmarshal(overrideJSON, &v); err != nil { return err } diff --git a/firstpartydata/first_party_data_test.go b/firstpartydata/first_party_data_test.go index ffa05a31d17..61369738bf3 100644 --- a/firstpartydata/first_party_data_test.go +++ b/firstpartydata/first_party_data_test.go @@ -10,6 +10,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -481,7 +482,7 @@ func TestExtractBidderConfigFPD(t *testing.T) { require.NoError(t, err, "Load Test File") givenRequestExtPrebid := &openrtb_ext.ExtRequestPrebid{} - err = json.Unmarshal(testFile.InputRequestData, givenRequestExtPrebid) + err = jsonutil.UnmarshalValid(testFile.InputRequestData, givenRequestExtPrebid) require.NoError(t, err, "Cannot Load Test Conditions") testRequest := &openrtb_ext.RequestExt{} @@ -537,11 +538,11 @@ func TestResolveFPD(t *testing.T) { require.NoError(t, err, "Load Test File") request := &openrtb2.BidRequest{} - err = json.Unmarshal(testFile.InputRequestData, &request) + err = jsonutil.UnmarshalValid(testFile.InputRequestData, &request) require.NoError(t, err, "Cannot Load Request") originalRequest := &openrtb2.BidRequest{} - err = json.Unmarshal(testFile.InputRequestData, &originalRequest) + err = jsonutil.UnmarshalValid(testFile.InputRequestData, &originalRequest) require.NoError(t, err, "Cannot Load Request") reqExtFPD := make(map[string][]byte) @@ -554,7 +555,7 @@ func TestResolveFPD(t *testing.T) { reqFPDSiteContentData := testFile.GlobalFPD[siteContentDataKey] if len(reqFPDSiteContentData) > 0 { var siteConData []openrtb2.Data - err = json.Unmarshal(reqFPDSiteContentData, &siteConData) + err = jsonutil.UnmarshalValid(reqFPDSiteContentData, &siteConData) if err != nil { t.Errorf("Unable to unmarshal site.content.data:") } @@ -564,7 +565,7 @@ func TestResolveFPD(t *testing.T) { reqFPDAppContentData := testFile.GlobalFPD[appContentDataKey] if len(reqFPDAppContentData) > 0 { var appConData []openrtb2.Data - err = json.Unmarshal(reqFPDAppContentData, &appConData) + err = jsonutil.UnmarshalValid(reqFPDAppContentData, &appConData) if err != nil { t.Errorf("Unable to unmarshal app.content.data: ") } @@ -574,7 +575,7 @@ func TestResolveFPD(t *testing.T) { reqFPDUserData := testFile.GlobalFPD[userDataKey] if len(reqFPDUserData) > 0 { var userData []openrtb2.Data - err = json.Unmarshal(reqFPDUserData, &userData) + err = jsonutil.UnmarshalValid(reqFPDUserData, &userData) if err != nil { t.Errorf("Unable to unmarshal app.content.data: ") } @@ -640,14 +641,14 @@ func TestExtractFPDForBidders(t *testing.T) { require.NoError(t, err, "Load Test File") var expectedRequest openrtb2.BidRequest - err = json.Unmarshal(testFile.OutputRequestData, &expectedRequest) + err = jsonutil.UnmarshalValid(testFile.OutputRequestData, &expectedRequest) if err != nil { t.Errorf("Unable to unmarshal input request: %s", path) } resultRequest := &openrtb_ext.RequestWrapper{} resultRequest.BidRequest = &openrtb2.BidRequest{} - err = json.Unmarshal(testFile.InputRequestData, resultRequest.BidRequest) + err = jsonutil.UnmarshalValid(testFile.InputRequestData, resultRequest.BidRequest) assert.NoError(t, err, "Error should be nil") resultFPD, errL := ExtractFPDForBidders(resultRequest) @@ -725,7 +726,7 @@ func TestResolveUser(t *testing.T) { globalFPD map[string][]byte openRtbGlobalFPD map[string][]openrtb2.Data expectedUser *openrtb2.User - expectedError string + expectError bool }{ { description: "FPD config and bid request user are not specified", @@ -768,7 +769,7 @@ func TestResolveUser(t *testing.T) { fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1"}`)}, bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDUserData":"inputFPDUserDataValue"}}`)}, globalFPD: map[string][]byte{userKey: []byte(`malformed`)}, - expectedError: "Invalid JSON Patch", + expectError: true, }, { description: "bid request and openrtb global fpd user are specified, no input user ext", @@ -837,18 +838,18 @@ func TestResolveUser(t *testing.T) { }, Ext: json.RawMessage(`{"key":"value","test":1}`), }, - expectedError: "invalid character 'm' looking for beginning of object key string", + expectError: true, }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { resultUser, err := resolveUser(test.fpdConfig, test.bidRequestUser, test.globalFPD, test.openRtbGlobalFPD, "bidderA") - if test.expectedError == "" { + if test.expectError { + assert.Error(t, err, "expected error incorrect") + } else { assert.NoError(t, err, "unexpected error returned") assert.Equal(t, test.expectedUser, resultUser, "Result user is incorrect") - } else { - assert.EqualError(t, err, test.expectedError, "expected error incorrect") } }) } @@ -862,16 +863,16 @@ func TestResolveSite(t *testing.T) { globalFPD map[string][]byte openRtbGlobalFPD map[string][]openrtb2.Data expectedSite *openrtb2.Site - expectedError string + expectError bool }{ { description: "FPD config and bid request site are not specified", expectedSite: nil, }, { - description: "FPD config site only is specified", - fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test"}`)}, - expectedError: "incorrect First Party Data for bidder bidderA: Site object is not defined in request, but defined in FPD config", + description: "FPD config site only is specified", + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test"}`)}, + expectError: true, }, { description: "FPD config and bid request site are specified", @@ -905,7 +906,7 @@ func TestResolveSite(t *testing.T) { fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test1"}`)}, bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDSiteData":"inputFPDSiteDataValue"}}`)}, globalFPD: map[string][]byte{siteKey: []byte(`malformed`)}, - expectedError: "Invalid JSON Patch", + expectError: true, }, { description: "bid request and openrtb global fpd site are specified, no input site ext", @@ -997,18 +998,18 @@ func TestResolveSite(t *testing.T) { }}, Ext: json.RawMessage(`{"key":"value","test":1}`), }, - expectedError: "invalid character 'm' looking for beginning of object key string", + expectError: true, }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { resultSite, err := resolveSite(test.fpdConfig, test.bidRequestSite, test.globalFPD, test.openRtbGlobalFPD, "bidderA") - if test.expectedError == "" { + if test.expectError { + assert.Error(t, err) + } else { assert.NoError(t, err, "unexpected error returned") assert.Equal(t, test.expectedSite, resultSite, "Result site is incorrect") - } else { - assert.EqualError(t, err, test.expectedError, "expected error incorrect") } }) } @@ -1022,16 +1023,16 @@ func TestResolveApp(t *testing.T) { globalFPD map[string][]byte openRtbGlobalFPD map[string][]openrtb2.Data expectedApp *openrtb2.App - expectedError string + expectError bool }{ { description: "FPD config and bid request app are not specified", expectedApp: nil, }, { - description: "FPD config app only is specified", - fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test"}`)}, - expectedError: "incorrect First Party Data for bidder bidderA: App object is not defined in request, but defined in FPD config", + description: "FPD config app only is specified", + fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test"}`)}, + expectError: true, }, { description: "FPD config and bid request app are specified", @@ -1065,7 +1066,7 @@ func TestResolveApp(t *testing.T) { fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test1"}`)}, bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDAppData":"inputFPDAppDataValue"}}`)}, globalFPD: map[string][]byte{appKey: []byte(`malformed`)}, - expectedError: "Invalid JSON Patch", + expectError: true, }, { description: "bid request and openrtb global fpd app are specified, no input app ext", @@ -1157,18 +1158,18 @@ func TestResolveApp(t *testing.T) { }}, Ext: json.RawMessage(`{"key":"value","test":1}`), }, - expectedError: "invalid character 'm' looking for beginning of object key string", + expectError: true, }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { resultApp, err := resolveApp(test.fpdConfig, test.bidRequestApp, test.globalFPD, test.openRtbGlobalFPD, "bidderA") - if test.expectedError == "" { - assert.NoError(t, err, "unexpected error returned") - assert.Equal(t, test.expectedApp, resultApp, "Result app is incorrect") + if test.expectError { + assert.Error(t, err) } else { - assert.EqualError(t, err, test.expectedError, "expected error incorrect") + assert.NoError(t, err) + assert.Equal(t, test.expectedApp, resultApp, "Result app is incorrect") } }) } @@ -1219,7 +1220,7 @@ func TestMergeUser(t *testing.T) { givenUser openrtb2.User givenFPD json.RawMessage expectedUser openrtb2.User - expectedErr string + expectError bool }{ { name: "empty", @@ -1243,7 +1244,7 @@ func TestMergeUser(t *testing.T) { name: "toplevel-ext-err", givenUser: openrtb2.User{ID: "1", Ext: []byte(`malformed`)}, givenFPD: []byte(`{"id":"2"}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-geo", @@ -1267,13 +1268,13 @@ func TestMergeUser(t *testing.T) { name: "nested-geo-ext-err", givenUser: openrtb2.User{Geo: &openrtb2.Geo{Ext: []byte(`malformed`)}}, givenFPD: []byte(`{"geo":{"ext":{"b":100,"c":3}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "fpd-err", givenUser: openrtb2.User{ID: "1", Ext: []byte(`{"a":1}`)}, givenFPD: []byte(`malformed`), - expectedErr: "invalid character 'm' looking for beginning of value", + expectError: true, }, } @@ -1281,11 +1282,11 @@ func TestMergeUser(t *testing.T) { t.Run(test.name, func(t *testing.T) { err := mergeUser(&test.givenUser, test.givenFPD) - if test.expectedErr == "" { - assert.NoError(t, err, "unexpected error returned") - assert.Equal(t, test.expectedUser, test.givenUser, "result user is incorrect") + if test.expectError { + assert.Error(t, err) } else { - assert.EqualError(t, err, test.expectedErr, "expected error incorrect") + assert.NoError(t, err) + assert.Equal(t, test.expectedUser, test.givenUser, "result user is incorrect") } }) } @@ -1297,7 +1298,7 @@ func TestMergeApp(t *testing.T) { givenApp openrtb2.App givenFPD json.RawMessage expectedApp openrtb2.App - expectedErr string + expectError bool }{ { name: "empty", @@ -1321,7 +1322,7 @@ func TestMergeApp(t *testing.T) { name: "toplevel-ext-err", givenApp: openrtb2.App{ID: "1", Ext: []byte(`malformed`)}, givenFPD: []byte(`{"id":"2"}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-publisher", @@ -1417,37 +1418,37 @@ func TestMergeApp(t *testing.T) { name: "nested-publisher-ext-err", givenApp: openrtb2.App{Publisher: &openrtb2.Publisher{Ext: []byte(`malformed`)}}, givenFPD: []byte(`{"publisher":{"ext":{"b":100,"c":3}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-ext-err", givenApp: openrtb2.App{Content: &openrtb2.Content{Ext: []byte(`malformed`)}}, givenFPD: []byte(`{"content":{"ext":{"b":100,"c":3}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-producer-ext-err", givenApp: openrtb2.App{Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`malformed`)}}}, givenFPD: []byte(`{"content":{"producer": {"ext":{"b":100,"c":3}}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-network-ext-err", givenApp: openrtb2.App{Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`malformed`)}}}, givenFPD: []byte(`{"content":{"network": {"ext":{"b":100,"c":3}}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-channel-ext-err", givenApp: openrtb2.App{Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`malformed`)}}}, givenFPD: []byte(`{"content":{"channelx": {"ext":{"b":100,"c":3}}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "fpd-err", givenApp: openrtb2.App{ID: "1", Ext: []byte(`{"a":1}`)}, givenFPD: []byte(`malformed`), - expectedErr: "invalid character 'm' looking for beginning of value", + expectError: true, }, } @@ -1455,11 +1456,11 @@ func TestMergeApp(t *testing.T) { t.Run(test.name, func(t *testing.T) { err := mergeApp(&test.givenApp, test.givenFPD) - if test.expectedErr == "" { - assert.NoError(t, err, "unexpected error returned") - assert.Equal(t, test.expectedApp, test.givenApp, " result app is incorrect") + if test.expectError { + assert.Error(t, err) } else { - assert.EqualError(t, err, test.expectedErr, "expected error incorrect") + assert.NoError(t, err) + assert.Equal(t, test.expectedApp, test.givenApp, " result app is incorrect") } }) } @@ -1471,13 +1472,13 @@ func TestMergeSite(t *testing.T) { givenSite openrtb2.Site givenFPD json.RawMessage expectedSite openrtb2.Site - expectedErr string + expectError bool }{ { name: "empty", givenSite: openrtb2.Site{}, givenFPD: []byte(`{}`), - expectedErr: "incorrect First Party Data for bidder BidderA: Site object cannot set empty page if req.site.id is empty", + expectError: true, }, { name: "toplevel", @@ -1495,7 +1496,7 @@ func TestMergeSite(t *testing.T) { name: "toplevel-ext-err", givenSite: openrtb2.Site{ID: "1", Ext: []byte(`malformed`)}, givenFPD: []byte(`{"id":"2"}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-publisher", @@ -1591,37 +1592,37 @@ func TestMergeSite(t *testing.T) { name: "nested-publisher-ext-err", givenSite: openrtb2.Site{ID: "1", Publisher: &openrtb2.Publisher{Ext: []byte(`malformed`)}}, givenFPD: []byte(`{"publisher":{"ext":{"b":100,"c":3}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-ext-err", givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Ext: []byte(`malformed`)}}, givenFPD: []byte(`{"content":{"ext":{"b":100,"c":3}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-producer-ext-err", givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`malformed`)}}}, givenFPD: []byte(`{"content":{"producer": {"ext":{"b":100,"c":3}}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-network-ext-err", givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`malformed`)}}}, givenFPD: []byte(`{"content":{"network": {"ext":{"b":100,"c":3}}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-channel-ext-err", givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`malformed`)}}}, givenFPD: []byte(`{"content":{"channelx": {"ext":{"b":100,"c":3}}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "fpd-err", givenSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1}`)}, givenFPD: []byte(`malformed`), - expectedErr: "invalid character 'm' looking for beginning of value", + expectError: true, }, } @@ -1629,11 +1630,11 @@ func TestMergeSite(t *testing.T) { t.Run(test.name, func(t *testing.T) { err := mergeSite(&test.givenSite, test.givenFPD, "BidderA") - if test.expectedErr == "" { - assert.NoError(t, err, "unexpected error returned") - assert.Equal(t, test.expectedSite, test.givenSite, " result Site is incorrect") + if test.expectError { + assert.Error(t, err) } else { - assert.EqualError(t, err, test.expectedErr, "expected error incorrect") + assert.NoError(t, err) + assert.Equal(t, test.expectedSite, test.givenSite, " result Site is incorrect") } }) } diff --git a/floors/floors_test.go b/floors/floors_test.go index 7f27e1a26d5..b3002c2f155 100644 --- a/floors/floors_test.go +++ b/floors/floors_test.go @@ -9,6 +9,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -447,7 +448,7 @@ func TestResolveFloors(t *testing.T) { } func printFloors(floors *openrtb_ext.PriceFloorRules) string { - fbytes, _ := json.Marshal(floors) + fbytes, _ := jsonutil.Marshal(floors) return string(fbytes) } diff --git a/floors/rule_test.go b/floors/rule_test.go index 27be6ef0dd2..7c484a7c95f 100644 --- a/floors/rule_test.go +++ b/floors/rule_test.go @@ -760,7 +760,7 @@ func TestGetMinFloorValue(t *testing.T) { }, want: 0.0, want1: "", - wantErr: errors.New("Error in getting FloorMin value : 'unexpected end of JSON input'"), + wantErr: errors.New("Error in getting FloorMin value : 'expects \" or n, but found \x00'"), }, } for _, tc := range testCases { diff --git a/gdpr/full_enforcement_test.go b/gdpr/full_enforcement_test.go index 61f6e8b8520..4a859ecaabb 100644 --- a/gdpr/full_enforcement_test.go +++ b/gdpr/full_enforcement_test.go @@ -1,7 +1,6 @@ package gdpr import ( - "encoding/json" "testing" "github.com/prebid/go-gdpr/consentconstants" @@ -10,6 +9,7 @@ import ( "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -961,7 +961,7 @@ func TestLegalBasisWithoutVendor(t *testing.T) { func getVendorList(t *testing.T) vendorlist.VendorList { GVL := makeVendorList() - marshaledGVL, err := json.Marshal(GVL) + marshaledGVL, err := jsonutil.Marshal(GVL) if err != nil { t.Fatalf("Failed to marshal GVL") } diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index a1dfb7fefb8..b8c73bbf9a4 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -2,7 +2,6 @@ package gdpr import ( "context" - "encoding/json" "net/http" "net/http/httptest" "strconv" @@ -13,6 +12,7 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/util/jsonutil" ) func TestFetcherDynamicLoadListExists(t *testing.T) { @@ -305,7 +305,7 @@ type vendor struct { } func MarshalVendorList(vendorList vendorList) string { - json, _ := json.Marshal(vendorList) + json, _ := jsonutil.Marshal(vendorList) return string(json) } diff --git a/go.mod b/go.mod index 6731c177ecf..e52946fbe1e 100644 --- a/go.mod +++ b/go.mod @@ -46,10 +46,13 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 1f76be7af31..eefe0228261 100644 --- a/go.sum +++ b/go.sum @@ -296,6 +296,7 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -356,9 +357,11 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= diff --git a/hooks/hookanalytics/analytics_test.go b/hooks/hookanalytics/analytics_test.go index 177a9335da9..abf5e7f2ddc 100644 --- a/hooks/hookanalytics/analytics_test.go +++ b/hooks/hookanalytics/analytics_test.go @@ -1,9 +1,9 @@ package hookanalytics import ( - "encoding/json" "testing" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -50,7 +50,7 @@ func TestAnalytics(t *testing.T) { Activity{Name: "define-blocks", Status: ActivityStatusError}, ) - gotAnalytics, err := json.Marshal(analytics) + gotAnalytics, err := jsonutil.Marshal(analytics) assert.NoError(t, err, "Failed to marshal analytics: %s", err) assert.JSONEq(t, string(expectedAnalytics), string(gotAnalytics)) } diff --git a/hooks/hookexecution/enricher.go b/hooks/hookexecution/enricher.go index 2978c21957d..c3dd5a23339 100644 --- a/hooks/hookexecution/enricher.go +++ b/hooks/hookexecution/enricher.go @@ -6,6 +6,7 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) @@ -52,7 +53,7 @@ func EnrichExtBidResponse( return ext, warnings, err } - response, err := json.Marshal(extPrebid{Prebid: extModules{Modules: modules}}) + response, err := jsonutil.Marshal(extPrebid{Prebid: extModules{Modules: modules}}) if err != nil { return ext, warnings, err } @@ -83,7 +84,7 @@ func GetModulesJSON( return nil, warnings, nil } - data, err := json.Marshal(modulesOutcome) + data, err := jsonutil.Marshal(modulesOutcome) return data, warnings, err } diff --git a/hooks/hookexecution/enricher_test.go b/hooks/hookexecution/enricher_test.go index 7bb19c2ecf2..24f0f1c1d45 100644 --- a/hooks/hookexecution/enricher_test.go +++ b/hooks/hookexecution/enricher_test.go @@ -10,6 +10,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/hooks/hookanalytics" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -258,7 +259,7 @@ func TestGetModulesJSON(t *testing.T) { assert.Empty(t, expectedResponse) } else { var expectedExtBidResponse openrtb_ext.ExtBidResponse - err := json.Unmarshal(expectedResponse, &expectedExtBidResponse) + err := jsonutil.UnmarshalValid(expectedResponse, &expectedExtBidResponse) assert.NoError(t, err, "Failed to unmarshal prebid response extension") assert.JSONEq(t, string(expectedExtBidResponse.Prebid.Modules), string(modules)) } @@ -271,7 +272,7 @@ func getStageOutcomes(t *testing.T, file string) []StageOutcome { var stageOutcomesTest []StageOutcomeTest data := readFile(t, file) - err := json.Unmarshal(data, &stageOutcomesTest) + err := jsonutil.UnmarshalValid(data, &stageOutcomesTest) require.NoError(t, err, "Failed to unmarshal stage outcomes: %s", err) for _, stageT := range stageOutcomesTest { diff --git a/hooks/hookexecution/test_utils.go b/hooks/hookexecution/test_utils.go index bd94d5778b4..2a604d851f5 100644 --- a/hooks/hookexecution/test_utils.go +++ b/hooks/hookexecution/test_utils.go @@ -4,6 +4,7 @@ import ( "encoding/json" "testing" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -16,8 +17,8 @@ func AssertEqualModulesData(t *testing.T, expectedData, actualData json.RawMessa var expectedModulesOutcome ModulesOutcome var actualModulesOutcome ModulesOutcome - assert.NoError(t, json.Unmarshal(expectedData, &expectedModulesOutcome), "Failed to unmarshal expected modules data.") - assert.NoError(t, json.Unmarshal(actualData, &actualModulesOutcome), "Failed to unmarshal actual modules data.") + assert.NoError(t, jsonutil.UnmarshalValid(expectedData, &expectedModulesOutcome), "Failed to unmarshal expected modules data.") + assert.NoError(t, jsonutil.UnmarshalValid(actualData, &actualModulesOutcome), "Failed to unmarshal actual modules data.") assert.Equal(t, expectedModulesOutcome.Errors, actualModulesOutcome.Errors, "Invalid error messages.") assert.Equal(t, expectedModulesOutcome.Warnings, actualModulesOutcome.Warnings, "Invalid warning messages.") diff --git a/hooks/plan_test.go b/hooks/plan_test.go index 5d2a504f0d1..8af49b42e17 100644 --- a/hooks/plan_test.go +++ b/hooks/plan_test.go @@ -2,12 +2,12 @@ package hooks import ( "context" - "encoding/json" "testing" "time" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -227,7 +227,7 @@ func TestPlanForRawAuctionStage(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { account := new(config.Account) - if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + if err := jsonutil.UnmarshalValid(test.giveAccountPlanData, &account.Hooks); err != nil { t.Fatal(err) } @@ -333,7 +333,7 @@ func TestPlanForProcessedAuctionStage(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { account := new(config.Account) - if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + if err := jsonutil.UnmarshalValid(test.giveAccountPlanData, &account.Hooks); err != nil { t.Fatal(err) } @@ -439,7 +439,7 @@ func TestPlanForBidderRequestStage(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { account := new(config.Account) - if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + if err := jsonutil.UnmarshalValid(test.giveAccountPlanData, &account.Hooks); err != nil { t.Fatal(err) } @@ -545,7 +545,7 @@ func TestPlanForRawBidderResponseStage(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { account := new(config.Account) - if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + if err := jsonutil.UnmarshalValid(test.giveAccountPlanData, &account.Hooks); err != nil { t.Fatal(err) } @@ -651,7 +651,7 @@ func TestPlanForAllProcessedBidResponsesStage(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { account := new(config.Account) - if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + if err := jsonutil.UnmarshalValid(test.giveAccountPlanData, &account.Hooks); err != nil { t.Fatal(err) } @@ -757,7 +757,7 @@ func TestPlanForAuctionResponseStage(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { account := new(config.Account) - if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + if err := jsonutil.UnmarshalValid(test.giveAccountPlanData, &account.Hooks); err != nil { t.Fatal(err) } @@ -779,12 +779,12 @@ func getPlanBuilder( var hostPlan config.HookExecutionPlan var defaultAccountPlan config.HookExecutionPlan - err = json.Unmarshal(hostPlanData, &hostPlan) + err = jsonutil.UnmarshalValid(hostPlanData, &hostPlan) if err != nil { return nil, err } - err = json.Unmarshal(accountPlanData, &defaultAccountPlan) + err = jsonutil.UnmarshalValid(accountPlanData, &defaultAccountPlan) if err != nil { return nil, err } diff --git a/modules/modules.go b/modules/modules.go index ac60ec58082..4e60a9eda32 100644 --- a/modules/modules.go +++ b/modules/modules.go @@ -8,6 +8,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/hooks" "github.com/prebid/prebid-server/modules/moduledeps" + "github.com/prebid/prebid-server/util/jsonutil" ) //go:generate go run ./generator/buildergen.go @@ -58,7 +59,7 @@ func (m *builder) Build( id := fmt.Sprintf("%s.%s", vendor, moduleName) if data, ok := cfg[vendor][moduleName]; ok { - if conf, err = json.Marshal(data); err != nil { + if conf, err = jsonutil.Marshal(data); err != nil { return nil, nil, fmt.Errorf(`failed to marshal "%s" module config: %s`, id, err) } diff --git a/modules/modules_test.go b/modules/modules_test.go index 1c70ce4badf..f88a48a2c44 100644 --- a/modules/modules_test.go +++ b/modules/modules_test.go @@ -85,7 +85,7 @@ func TestModuleBuilderBuild(t *testing.T) { givenConfig: map[string]map[string]interface{}{vendor: {moduleName: math.Inf(1)}}, expectedHookRepo: nil, expectedModulesStages: nil, - expectedErr: fmt.Errorf(`failed to marshal "%s.%s" module config: json: unsupported value: +Inf`, vendor, moduleName), + expectedErr: fmt.Errorf(`failed to marshal "%s.%s" module config: unsupported value: +Inf`, vendor, moduleName), }, } diff --git a/modules/prebid/ortb2blocking/config.go b/modules/prebid/ortb2blocking/config.go index cefd436b402..74841eb5b33 100644 --- a/modules/prebid/ortb2blocking/config.go +++ b/modules/prebid/ortb2blocking/config.go @@ -5,11 +5,12 @@ import ( "fmt" "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/prebid-server/util/jsonutil" ) func newConfig(data json.RawMessage) (config, error) { var cfg config - if err := json.Unmarshal(data, &cfg); err != nil { + if err := jsonutil.UnmarshalValid(data, &cfg); err != nil { return cfg, fmt.Errorf("failed to parse config: %s", err) } return cfg, nil @@ -112,7 +113,7 @@ type Override struct { func (o *Override) UnmarshalJSON(bytes []byte) error { var overrideData interface{} - if err := json.Unmarshal(bytes, &overrideData); err != nil { + if err := jsonutil.UnmarshalValid(bytes, &overrideData); err != nil { return err } diff --git a/modules/prebid/ortb2blocking/module_test.go b/modules/prebid/ortb2blocking/module_test.go index b47f9f58a02..ecae5ca8c0a 100644 --- a/modules/prebid/ortb2blocking/module_test.go +++ b/modules/prebid/ortb2blocking/module_test.go @@ -488,7 +488,7 @@ func TestHandleBidderRequestHook(t *testing.T) { bidRequest: &openrtb2.BidRequest{}, expectedBidRequest: &openrtb2.BidRequest{}, expectedHookResult: hookstage.HookResult[hookstage.BidderRequestPayload]{}, - expectedError: errors.New("failed to parse config: invalid character '.' looking for beginning of value"), + expectedError: errors.New("failed to parse config: expect { or n, but found ."), }, { description: "Expect error if nil BidRequest provided", diff --git a/openrtb_ext/convert_down_test.go b/openrtb_ext/convert_down_test.go index ea21468e737..3b78337df8b 100644 --- a/openrtb_ext/convert_down_test.go +++ b/openrtb_ext/convert_down_test.go @@ -6,6 +6,7 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/errortypes" "github.com/stretchr/testify/assert" ) @@ -14,7 +15,7 @@ func TestConvertDownTo25(t *testing.T) { name string givenRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest - expectedErr string + expectedErrType error }{ { name: "2.6-to-2.5", @@ -87,12 +88,12 @@ func TestConvertDownTo25(t *testing.T) { }, }, { - name: "malformed-shhain", + name: "malformed-schain", givenRequest: openrtb2.BidRequest{ ID: "anyID", Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}, Ext: json.RawMessage(`malformed`)}, }, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErrType: &errortypes.FailedToUnmarshal{}, }, { name: "malformed-gdpr", @@ -100,7 +101,7 @@ func TestConvertDownTo25(t *testing.T) { ID: "anyID", Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), Ext: json.RawMessage(`malformed`)}, }, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErrType: &errortypes.FailedToUnmarshal{}, }, { name: "malformed-consent", @@ -108,7 +109,7 @@ func TestConvertDownTo25(t *testing.T) { ID: "anyID", User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`malformed`)}, }, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErrType: &errortypes.FailedToUnmarshal{}, }, { name: "malformed-usprivacy", @@ -116,7 +117,7 @@ func TestConvertDownTo25(t *testing.T) { ID: "anyID", Regs: &openrtb2.Regs{USPrivacy: "3", Ext: json.RawMessage(`malformed`)}, }, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErrType: &errortypes.FailedToUnmarshal{}, }, { name: "malformed-eid", @@ -124,7 +125,7 @@ func TestConvertDownTo25(t *testing.T) { ID: "anyID", User: &openrtb2.User{EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`malformed`)}, }, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErrType: &errortypes.FailedToUnmarshal{}, }, { name: "malformed-imp", @@ -132,7 +133,7 @@ func TestConvertDownTo25(t *testing.T) { ID: "anyID", Imp: []openrtb2.Imp{{Rwdd: 1, Ext: json.RawMessage(`malformed`)}}, }, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -141,8 +142,8 @@ func TestConvertDownTo25(t *testing.T) { w := &RequestWrapper{BidRequest: &test.givenRequest} err := ConvertDownTo25(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildRequest(), "error") assert.Equal(t, test.expectedRequest, *w.BidRequest, "result") @@ -162,7 +163,7 @@ func TestMoveSupplyChainFrom26To25(t *testing.T) { name string givenRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest - expectedErr string + expectedErrType error }{ { name: "notpresent-source", @@ -185,9 +186,9 @@ func TestMoveSupplyChainFrom26To25(t *testing.T) { expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1Json}}, }, { - name: "malformed", - givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: json.RawMessage(`malformed`)}}, - expectedErr: "invalid character 'm' looking for beginning of value", + name: "malformed", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: json.RawMessage(`malformed`)}}, + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -196,8 +197,8 @@ func TestMoveSupplyChainFrom26To25(t *testing.T) { w := &RequestWrapper{BidRequest: &test.givenRequest} err := moveSupplyChainFrom26To25(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildRequest(), "error") assert.Equal(t, test.expectedRequest, *w.BidRequest, "result") @@ -211,7 +212,7 @@ func TestMoveGDPRFrom26To25(t *testing.T) { name string givenRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest - expectedErr string + expectedErrType error }{ { name: "notpresent-regs", @@ -234,9 +235,9 @@ func TestMoveGDPRFrom26To25(t *testing.T) { expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":0}`)}}, }, { - name: "malformed", - givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`malformed`)}}, - expectedErr: "invalid character 'm' looking for beginning of value", + name: "malformed", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`malformed`)}}, + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -245,8 +246,8 @@ func TestMoveGDPRFrom26To25(t *testing.T) { w := &RequestWrapper{BidRequest: &test.givenRequest} err := moveGDPRFrom26To25(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildRequest(), "error") assert.Equal(t, test.expectedRequest, *w.BidRequest, "result") @@ -260,7 +261,7 @@ func TestMoveConsentFrom26To25(t *testing.T) { name string givenRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest - expectedErr string + expectedErrType error }{ { name: "notpresent-user", @@ -283,9 +284,9 @@ func TestMoveConsentFrom26To25(t *testing.T) { expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1"}`)}}, }, { - name: "malformed", - givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`malformed`)}}, - expectedErr: "invalid character 'm' looking for beginning of value", + name: "malformed", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`malformed`)}}, + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -294,8 +295,8 @@ func TestMoveConsentFrom26To25(t *testing.T) { w := &RequestWrapper{BidRequest: &test.givenRequest} err := moveConsentFrom26To25(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildRequest(), "error") assert.Equal(t, test.expectedRequest, *w.BidRequest, "result") @@ -309,7 +310,7 @@ func TestMoveUSPrivacyFrom26To25(t *testing.T) { name string givenRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest - expectedErr string + expectedErrType error }{ { name: "notpresent-regs", @@ -332,9 +333,9 @@ func TestMoveUSPrivacyFrom26To25(t *testing.T) { expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1"}`)}}, }, { - name: "malformed", - givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`malformed`)}}, - expectedErr: "invalid character 'm' looking for beginning of value", + name: "malformed", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`malformed`)}}, + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -343,8 +344,8 @@ func TestMoveUSPrivacyFrom26To25(t *testing.T) { w := &RequestWrapper{BidRequest: &test.givenRequest} err := moveUSPrivacyFrom26To25(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildRequest(), "error") assert.Equal(t, test.expectedRequest, *w.BidRequest, "result") @@ -364,7 +365,7 @@ func TestMoveEIDFrom26To25(t *testing.T) { name string givenRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest - expectedErr string + expectedErrType error }{ { name: "notpresent-user", @@ -392,9 +393,9 @@ func TestMoveEIDFrom26To25(t *testing.T) { expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: eid1Json}}, }, { - name: "malformed", - givenRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: json.RawMessage(`malformed`)}}, - expectedErr: "invalid character 'm' looking for beginning of value", + name: "malformed", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: json.RawMessage(`malformed`)}}, + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -403,8 +404,8 @@ func TestMoveEIDFrom26To25(t *testing.T) { w := &RequestWrapper{BidRequest: &test.givenRequest} err := moveEIDFrom26To25(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildRequest(), "error") assert.Equal(t, test.expectedRequest, *w.BidRequest, "result") @@ -415,10 +416,10 @@ func TestMoveEIDFrom26To25(t *testing.T) { func TestMoveRewardedFrom26ToPrebidExt(t *testing.T) { testCases := []struct { - name string - givenImp openrtb2.Imp - expectedImp openrtb2.Imp - expectedErr string + name string + givenImp openrtb2.Imp + expectedImp openrtb2.Imp + expectedErrType error }{ { name: "notpresent-prebid", @@ -436,9 +437,9 @@ func TestMoveRewardedFrom26ToPrebidExt(t *testing.T) { expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}, }, { - name: "Malformed", - givenImp: openrtb2.Imp{Rwdd: 1, Ext: json.RawMessage(`malformed`)}, - expectedErr: "invalid character 'm' looking for beginning of value", + name: "Malformed", + givenImp: openrtb2.Imp{Rwdd: 1, Ext: json.RawMessage(`malformed`)}, + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -447,8 +448,8 @@ func TestMoveRewardedFrom26ToPrebidExt(t *testing.T) { w := &ImpWrapper{Imp: &test.givenImp} err := moveRewardedFrom26ToPrebidExt(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildImp(), "error") assert.Equal(t, test.expectedImp, *w.Imp, "result") diff --git a/openrtb_ext/convert_up_test.go b/openrtb_ext/convert_up_test.go index d3ba034d07e..3cafe8c1612 100644 --- a/openrtb_ext/convert_up_test.go +++ b/openrtb_ext/convert_up_test.go @@ -20,7 +20,7 @@ func TestConvertUpTo26(t *testing.T) { givenRequest: openrtb2.BidRequest{ Ext: json.RawMessage(`malformed`), }, - expectedErr: "req.ext is invalid: invalid character 'm' looking for beginning of value", + expectedErr: "req.ext is invalid: expect { or n, but found m", }, { description: "2.4 -> 2.6", @@ -120,27 +120,27 @@ func TestConvertUpEnsureExt(t *testing.T) { { description: "Ext", givenRequest: openrtb2.BidRequest{Ext: json.RawMessage("malformed")}, - expectedErr: "req.ext is invalid: invalid character 'm' looking for beginning of value", + expectedErr: "req.ext is invalid: expect { or n, but found m", }, { description: "Source.Ext", givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage("malformed")}}, - expectedErr: "req.source.ext is invalid: invalid character 'm' looking for beginning of value", + expectedErr: "req.source.ext is invalid: expect { or n, but found m", }, { description: "Regs.Ext", givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage("malformed")}}, - expectedErr: "req.regs.ext is invalid: invalid character 'm' looking for beginning of value", + expectedErr: "req.regs.ext is invalid: expect { or n, but found m", }, { description: "User.Ext", givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage("malformed")}}, - expectedErr: "req.user.ext is invalid: invalid character 'm' looking for beginning of value", + expectedErr: "req.user.ext is invalid: expect { or n, but found m", }, { description: "Imp.Ext", givenRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: json.RawMessage("malformed")}}}, - expectedErr: "imp[0].imp.ext is invalid: invalid character 'm' looking for beginning of value", + expectedErr: "imp[0].imp.ext is invalid: expect { or n, but found m", }, } diff --git a/openrtb_ext/deal_tier.go b/openrtb_ext/deal_tier.go index 45285d21663..df386916b77 100644 --- a/openrtb_ext/deal_tier.go +++ b/openrtb_ext/deal_tier.go @@ -1,9 +1,8 @@ package openrtb_ext import ( - "encoding/json" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/util/jsonutil" ) // DealTier defines the configuration of a deal tier. @@ -34,7 +33,7 @@ func ReadDealTiersFromImp(imp openrtb2.Imp) (DealTierBidderMap, error) { } `json:"bidder"` } `json:"prebid"` } - if err := json.Unmarshal(imp.Ext, &impPrebidExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &impPrebidExt); err != nil { return nil, err } for bidder, param := range impPrebidExt.Prebid.Bidders { diff --git a/openrtb_ext/deal_tier_test.go b/openrtb_ext/deal_tier_test.go index 0046b788ece..dabecf6a9e7 100644 --- a/openrtb_ext/deal_tier_test.go +++ b/openrtb_ext/deal_tier_test.go @@ -5,15 +5,16 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/errortypes" "github.com/stretchr/testify/assert" ) func TestReadDealTiersFromImp(t *testing.T) { testCases := []struct { - description string - impExt json.RawMessage - expectedResult DealTierBidderMap - expectedError string + description string + impExt json.RawMessage + expectedResult DealTierBidderMap + expectedErrorType error }{ { description: "Nil", @@ -71,9 +72,9 @@ func TestReadDealTiersFromImp(t *testing.T) { expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext.prebid.bidder - error", - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": "wrong type", "placementId": 12345}}}}`), - expectedError: "json: cannot unmarshal string into Go struct field .prebid.bidder.dealTier of type openrtb_ext.DealTier", + description: "imp.ext.prebid.bidder - error", + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": "wrong type", "placementId": 12345}}}}`), + expectedErrorType: &errortypes.FailedToUnmarshal{}, }, } @@ -84,10 +85,10 @@ func TestReadDealTiersFromImp(t *testing.T) { assert.Equal(t, test.expectedResult, result, test.description+":result") - if len(test.expectedError) == 0 { - assert.NoError(t, err, test.description+":error") + if test.expectedErrorType != nil { + assert.IsType(t, test.expectedErrorType, err) } else { - assert.EqualError(t, err, test.expectedError, test.description+":error") + assert.NoError(t, err, test.description+":error") } } } diff --git a/openrtb_ext/device_test.go b/openrtb_ext/device_test.go index 1a3dbe8e2f4..86a0e1d7ff2 100644 --- a/openrtb_ext/device_test.go +++ b/openrtb_ext/device_test.go @@ -4,30 +4,31 @@ import ( "encoding/json" "testing" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) func TestInvalidDeviceExt(t *testing.T) { var s ExtDevice - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":105}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":true,"minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":null,"minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":"75","minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":105}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":true,"minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":null,"minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":"75","minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":85}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":85,"minheightperc":-5}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":85,"minheightperc":false}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":85,"minheightperc":"75"}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":85}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":85,"minheightperc":-5}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":85,"minheightperc":false}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":85,"minheightperc":"75"}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") } func TestValidDeviceExt(t *testing.T) { var s ExtDevice - assert.NoError(t, json.Unmarshal([]byte(`{"prebid":{}}`), &s)) + assert.NoError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{}}`), &s)) assert.Nil(t, s.Prebid.Interstitial) - assert.NoError(t, json.Unmarshal([]byte(`{}`), &s)) + assert.NoError(t, jsonutil.UnmarshalValid([]byte(`{}`), &s)) assert.Nil(t, s.Prebid.Interstitial) - assert.NoError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":75,"minheightperc":60}}}`), &s)) + assert.NoError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":75,"minheightperc":60}}}`), &s)) assert.EqualValues(t, 75, s.Prebid.Interstitial.MinWidthPerc) assert.EqualValues(t, 60, s.Prebid.Interstitial.MinHeightPerc) } diff --git a/openrtb_ext/imp_appnexus.go b/openrtb_ext/imp_appnexus.go index d9549e74750..02476d6bcf0 100644 --- a/openrtb_ext/imp_appnexus.go +++ b/openrtb_ext/imp_appnexus.go @@ -45,7 +45,7 @@ func (ks *ExtImpAppnexusKeywords) UnmarshalJSON(b []byte) error { switch b[0] { case '{': var results map[string][]string - if err := json.Unmarshal(b, &results); err != nil { + if err := jsonutil.UnmarshalValid(b, &results); err != nil { return err } @@ -64,7 +64,7 @@ func (ks *ExtImpAppnexusKeywords) UnmarshalJSON(b []byte) error { } case '[': var results []extImpAppnexusKeyVal - if err := json.Unmarshal(b, &results); err != nil { + if err := jsonutil.UnmarshalValid(b, &results); err != nil { return err } var kvs strings.Builder @@ -82,7 +82,7 @@ func (ks *ExtImpAppnexusKeywords) UnmarshalJSON(b []byte) error { } case '"': var keywords string - if err := json.Unmarshal(b, &keywords); err != nil { + if err := jsonutil.UnmarshalValid(b, &keywords); err != nil { return err } *ks = ExtImpAppnexusKeywords(keywords) diff --git a/openrtb_ext/imp_appnexus_test.go b/openrtb_ext/imp_appnexus_test.go index cbd6779d5da..a226c0d8410 100644 --- a/openrtb_ext/imp_appnexus_test.go +++ b/openrtb_ext/imp_appnexus_test.go @@ -1,9 +1,9 @@ package openrtb_ext import ( - "encoding/json" "testing" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -31,7 +31,7 @@ func TestKeywordsUnmarshalJSON(t *testing.T) { for _, test := range validTestCases { var keywords keywords - assert.NoError(t, json.Unmarshal(test.input, &keywords), test.desc) + assert.NoError(t, jsonutil.UnmarshalValid(test.input, &keywords), test.desc) assert.Equal(t, test.expected, keywords.Keywords.String()) } @@ -42,6 +42,6 @@ func TestKeywordsUnmarshalJSON(t *testing.T) { for _, test := range invalidTestCases { var keywords keywords - assert.Error(t, json.Unmarshal(test.input, &keywords), test.desc) + assert.Error(t, jsonutil.UnmarshalValid(test.input, &keywords), test.desc) } } diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index f5418ba4d1f..3a41fdf0dbb 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/maputil" "github.com/prebid/prebid-server/util/ptrutil" "github.com/prebid/prebid-server/util/sliceutil" @@ -234,7 +235,7 @@ func (pg *PriceGranularity) UnmarshalJSON(b []byte) error { // price granularity used to be a string referencing a predefined value, try to parse // and map the legacy string before falling back to the modern custom model. legacyID := "" - if err := json.Unmarshal(b, &legacyID); err == nil { + if err := jsonutil.Unmarshal(b, &legacyID); err == nil { if legacyValue, ok := NewPriceGranularityFromLegacyID(legacyID); ok { *pg = legacyValue return nil @@ -243,7 +244,7 @@ func (pg *PriceGranularity) UnmarshalJSON(b []byte) error { // use a type-alias to avoid calling back into this UnmarshalJSON implementation modernValue := PriceGranularityRaw{} - err := json.Unmarshal(b, &modernValue) + err := jsonutil.Unmarshal(b, &modernValue) if err == nil { *pg = (PriceGranularity)(modernValue) } diff --git a/openrtb_ext/request_test.go b/openrtb_ext/request_test.go index a05bae3a6bf..ad6a655b022 100644 --- a/openrtb_ext/request_test.go +++ b/openrtb_ext/request_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -37,7 +38,7 @@ func TestGranularityUnmarshal(t *testing.T) { for _, tg := range testGroups { for i, tc := range tg.in { var resolved PriceGranularity - err := json.Unmarshal(tc.json, &resolved) + err := jsonutil.UnmarshalValid(tc.json, &resolved) // Assert validation error if tg.expectError && !assert.Errorf(t, err, "%s test case %d", tg.desc, i) { diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go index f4ef69b0523..09321e5b0b3 100644 --- a/openrtb_ext/request_wrapper.go +++ b/openrtb_ext/request_wrapper.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/maputil" "github.com/prebid/prebid-server/util/ptrutil" "github.com/prebid/prebid-server/util/sliceutil" @@ -440,13 +441,13 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &ue.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &ue.ext); err != nil { return err } consentJson, hasConsent := ue.ext[consentKey] - if hasConsent { - if err := json.Unmarshal(consentJson, &ue.consent); err != nil { + if hasConsent && consentJson != nil { + if err := jsonutil.Unmarshal(consentJson, &ue.consent); err != nil { return err } } @@ -454,7 +455,9 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error { prebidJson, hasPrebid := ue.ext[prebidKey] if hasPrebid { ue.prebid = &ExtUserPrebid{} - if err := json.Unmarshal(prebidJson, ue.prebid); err != nil { + } + if prebidJson != nil { + if err := jsonutil.Unmarshal(prebidJson, ue.prebid); err != nil { return err } } @@ -462,21 +465,29 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error { eidsJson, hasEids := ue.ext[eidsKey] if hasEids { ue.eids = &[]openrtb2.EID{} - if err := json.Unmarshal(eidsJson, ue.eids); err != nil { + } + if eidsJson != nil { + if err := jsonutil.Unmarshal(eidsJson, ue.eids); err != nil { return err } } - if consentedProviderSettingsJson, hasCPSettings := ue.ext[consentedProvidersSettingsStringKey]; hasCPSettings { + consentedProviderSettingsInJson, hasCPSettingsIn := ue.ext[consentedProvidersSettingsStringKey] + if hasCPSettingsIn { ue.consentedProvidersSettingsIn = &ConsentedProvidersSettingsIn{} - if err := json.Unmarshal(consentedProviderSettingsJson, ue.consentedProvidersSettingsIn); err != nil { + } + if consentedProviderSettingsInJson != nil { + if err := jsonutil.Unmarshal(consentedProviderSettingsInJson, ue.consentedProvidersSettingsIn); err != nil { return err } } - if consentedProviderSettingsJson, hasCPSettings := ue.ext[consentedProvidersSettingsListKey]; hasCPSettings { + consentedProviderSettingsOutJson, hasCPSettingsOut := ue.ext[consentedProvidersSettingsListKey] + if hasCPSettingsOut { ue.consentedProvidersSettingsOut = &ConsentedProvidersSettingsOut{} - if err := json.Unmarshal(consentedProviderSettingsJson, ue.consentedProvidersSettingsOut); err != nil { + } + if consentedProviderSettingsOutJson != nil { + if err := jsonutil.Unmarshal(consentedProviderSettingsOutJson, ue.consentedProvidersSettingsOut); err != nil { return err } } @@ -487,7 +498,7 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error { func (ue *UserExt) marshal() (json.RawMessage, error) { if ue.consentDirty { if ue.consent != nil && len(*ue.consent) > 0 { - consentJson, err := json.Marshal(ue.consent) + consentJson, err := jsonutil.Marshal(ue.consent) if err != nil { return nil, err } @@ -500,7 +511,7 @@ func (ue *UserExt) marshal() (json.RawMessage, error) { if ue.prebidDirty { if ue.prebid != nil { - prebidJson, err := json.Marshal(ue.prebid) + prebidJson, err := jsonutil.Marshal(ue.prebid) if err != nil { return nil, err } @@ -517,7 +528,7 @@ func (ue *UserExt) marshal() (json.RawMessage, error) { if ue.consentedProvidersSettingsInDirty { if ue.consentedProvidersSettingsIn != nil { - cpSettingsJson, err := json.Marshal(ue.consentedProvidersSettingsIn) + cpSettingsJson, err := jsonutil.Marshal(ue.consentedProvidersSettingsIn) if err != nil { return nil, err } @@ -534,7 +545,7 @@ func (ue *UserExt) marshal() (json.RawMessage, error) { if ue.consentedProvidersSettingsOutDirty { if ue.consentedProvidersSettingsOut != nil { - cpSettingsJson, err := json.Marshal(ue.consentedProvidersSettingsOut) + cpSettingsJson, err := jsonutil.Marshal(ue.consentedProvidersSettingsOut) if err != nil { return nil, err } @@ -551,7 +562,7 @@ func (ue *UserExt) marshal() (json.RawMessage, error) { if ue.eidsDirty { if ue.eids != nil && len(*ue.eids) > 0 { - eidsJson, err := json.Marshal(ue.eids) + eidsJson, err := jsonutil.Marshal(ue.eids) if err != nil { return nil, err } @@ -566,7 +577,7 @@ func (ue *UserExt) marshal() (json.RawMessage, error) { if len(ue.ext) == 0 { return nil, nil } - return json.Marshal(ue.ext) + return jsonutil.Marshal(ue.ext) } func (ue *UserExt) Dirty() bool { @@ -726,14 +737,16 @@ func (re *RequestExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &re.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &re.ext); err != nil { return err } prebidJson, hasPrebid := re.ext[prebidKey] if hasPrebid { re.prebid = &ExtRequestPrebid{} - if err := json.Unmarshal(prebidJson, re.prebid); err != nil { + } + if prebidJson != nil { + if err := jsonutil.Unmarshal(prebidJson, re.prebid); err != nil { return err } } @@ -741,7 +754,9 @@ func (re *RequestExt) unmarshal(extJson json.RawMessage) error { schainJson, hasSChain := re.ext[schainKey] if hasSChain { re.schain = &openrtb2.SupplyChain{} - if err := json.Unmarshal(schainJson, re.schain); err != nil { + } + if schainJson != nil { + if err := jsonutil.Unmarshal(schainJson, re.schain); err != nil { return err } } @@ -752,7 +767,7 @@ func (re *RequestExt) unmarshal(extJson json.RawMessage) error { func (re *RequestExt) marshal() (json.RawMessage, error) { if re.prebidDirty { if re.prebid != nil { - prebidJson, err := json.Marshal(re.prebid) + prebidJson, err := jsonutil.Marshal(re.prebid) if err != nil { return nil, err } @@ -769,7 +784,7 @@ func (re *RequestExt) marshal() (json.RawMessage, error) { if re.schainDirty { if re.schain != nil { - schainJson, err := json.Marshal(re.schain) + schainJson, err := jsonutil.Marshal(re.schain) if err != nil { return nil, err } @@ -788,7 +803,7 @@ func (re *RequestExt) marshal() (json.RawMessage, error) { if len(re.ext) == 0 { return nil, nil } - return json.Marshal(re.ext) + return jsonutil.Marshal(re.ext) } func (re *RequestExt) Dirty() bool { @@ -880,14 +895,16 @@ func (de *DeviceExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &de.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &de.ext); err != nil { return err } prebidJson, hasPrebid := de.ext[prebidKey] if hasPrebid { de.prebid = &ExtDevicePrebid{} - if err := json.Unmarshal(prebidJson, de.prebid); err != nil { + } + if prebidJson != nil { + if err := jsonutil.Unmarshal(prebidJson, de.prebid); err != nil { return err } } @@ -898,7 +915,7 @@ func (de *DeviceExt) unmarshal(extJson json.RawMessage) error { func (de *DeviceExt) marshal() (json.RawMessage, error) { if de.prebidDirty { if de.prebid != nil { - prebidJson, err := json.Marshal(de.prebid) + prebidJson, err := jsonutil.Marshal(de.prebid) if err != nil { return nil, err } @@ -917,7 +934,7 @@ func (de *DeviceExt) marshal() (json.RawMessage, error) { if len(de.ext) == 0 { return nil, nil } - return json.Marshal(de.ext) + return jsonutil.Marshal(de.ext) } func (de *DeviceExt) Dirty() bool { @@ -992,14 +1009,16 @@ func (ae *AppExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &ae.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &ae.ext); err != nil { return err } prebidJson, hasPrebid := ae.ext[prebidKey] if hasPrebid { ae.prebid = &ExtAppPrebid{} - if err := json.Unmarshal(prebidJson, ae.prebid); err != nil { + } + if prebidJson != nil { + if err := jsonutil.Unmarshal(prebidJson, ae.prebid); err != nil { return err } } @@ -1010,7 +1029,7 @@ func (ae *AppExt) unmarshal(extJson json.RawMessage) error { func (ae *AppExt) marshal() (json.RawMessage, error) { if ae.prebidDirty { if ae.prebid != nil { - prebidJson, err := json.Marshal(ae.prebid) + prebidJson, err := jsonutil.Marshal(ae.prebid) if err != nil { return nil, err } @@ -1029,7 +1048,7 @@ func (ae *AppExt) marshal() (json.RawMessage, error) { if len(ae.ext) == 0 { return nil, nil } - return json.Marshal(ae.ext) + return jsonutil.Marshal(ae.ext) } func (ae *AppExt) Dirty() bool { @@ -1096,7 +1115,7 @@ func (de *DOOHExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &de.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &de.ext); err != nil { return err } @@ -1108,7 +1127,7 @@ func (de *DOOHExt) marshal() (json.RawMessage, error) { if len(de.ext) == 0 { return nil, nil } - return json.Marshal(de.ext) + return jsonutil.Marshal(de.ext) } func (de *DOOHExt) Dirty() bool { @@ -1163,20 +1182,20 @@ func (re *RegExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &re.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &re.ext); err != nil { return err } gdprJson, hasGDPR := re.ext[gdprKey] - if hasGDPR { - if err := json.Unmarshal(gdprJson, &re.gdpr); err != nil { + if hasGDPR && gdprJson != nil { + if err := jsonutil.Unmarshal(gdprJson, &re.gdpr); err != nil { return errors.New("gdpr must be an integer") } } uspJson, hasUsp := re.ext[us_privacyKey] - if hasUsp { - if err := json.Unmarshal(uspJson, &re.usPrivacy); err != nil { + if hasUsp && uspJson != nil { + if err := jsonutil.Unmarshal(uspJson, &re.usPrivacy); err != nil { return err } } @@ -1187,7 +1206,7 @@ func (re *RegExt) unmarshal(extJson json.RawMessage) error { func (re *RegExt) marshal() (json.RawMessage, error) { if re.gdprDirty { if re.gdpr != nil { - rawjson, err := json.Marshal(re.gdpr) + rawjson, err := jsonutil.Marshal(re.gdpr) if err != nil { return nil, err } @@ -1200,7 +1219,7 @@ func (re *RegExt) marshal() (json.RawMessage, error) { if re.usPrivacyDirty { if len(re.usPrivacy) > 0 { - rawjson, err := json.Marshal(re.usPrivacy) + rawjson, err := jsonutil.Marshal(re.usPrivacy) if err != nil { return nil, err } @@ -1215,7 +1234,7 @@ func (re *RegExt) marshal() (json.RawMessage, error) { if len(re.ext) == 0 { return nil, nil } - return json.Marshal(re.ext) + return jsonutil.Marshal(re.ext) } func (re *RegExt) Dirty() bool { @@ -1290,13 +1309,13 @@ func (se *SiteExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &se.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &se.ext); err != nil { return err } ampJson, hasAmp := se.ext[ampKey] - if hasAmp { - if err := json.Unmarshal(ampJson, &se.amp); err != nil { + if hasAmp && ampJson != nil { + if err := jsonutil.Unmarshal(ampJson, &se.amp); err != nil { return errors.New(`request.site.ext.amp must be either 1, 0, or undefined`) } } @@ -1307,7 +1326,7 @@ func (se *SiteExt) unmarshal(extJson json.RawMessage) error { func (se *SiteExt) marshal() (json.RawMessage, error) { if se.ampDirty { if se.amp != nil { - ampJson, err := json.Marshal(se.amp) + ampJson, err := jsonutil.Marshal(se.amp) if err != nil { return nil, err } @@ -1322,7 +1341,7 @@ func (se *SiteExt) marshal() (json.RawMessage, error) { if len(se.ext) == 0 { return nil, nil } - return json.Marshal(se.ext) + return jsonutil.Marshal(se.ext) } func (se *SiteExt) Dirty() bool { @@ -1385,13 +1404,13 @@ func (se *SourceExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &se.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &se.ext); err != nil { return err } schainJson, hasSChain := se.ext[schainKey] - if hasSChain { - if err := json.Unmarshal(schainJson, &se.schain); err != nil { + if hasSChain && schainJson != nil { + if err := jsonutil.Unmarshal(schainJson, &se.schain); err != nil { return err } } @@ -1402,7 +1421,7 @@ func (se *SourceExt) unmarshal(extJson json.RawMessage) error { func (se *SourceExt) marshal() (json.RawMessage, error) { if se.schainDirty { if se.schain != nil { - schainJson, err := json.Marshal(se.schain) + schainJson, err := jsonutil.Marshal(se.schain) if err != nil { return nil, err } @@ -1421,7 +1440,7 @@ func (se *SourceExt) marshal() (json.RawMessage, error) { if len(se.ext) == 0 { return nil, nil } - return json.Marshal(se.ext) + return jsonutil.Marshal(se.ext) } func (se *SourceExt) Dirty() bool { @@ -1550,14 +1569,16 @@ func (e *ImpExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &e.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &e.ext); err != nil { return err } prebidJson, hasPrebid := e.ext[prebidKey] if hasPrebid { e.prebid = &ExtImpPrebid{} - if err := json.Unmarshal(prebidJson, e.prebid); err != nil { + } + if prebidJson != nil { + if err := jsonutil.Unmarshal(prebidJson, e.prebid); err != nil { return err } } @@ -1565,21 +1586,23 @@ func (e *ImpExt) unmarshal(extJson json.RawMessage) error { dataJson, hasData := e.ext[dataKey] if hasData { e.data = &ExtImpData{} - if err := json.Unmarshal(dataJson, e.data); err != nil { + } + if dataJson != nil { + if err := jsonutil.Unmarshal(dataJson, e.data); err != nil { return err } } tidJson, hasTid := e.ext["tid"] - if hasTid { - if err := json.Unmarshal(tidJson, &e.tid); err != nil { + if hasTid && tidJson != nil { + if err := jsonutil.Unmarshal(tidJson, &e.tid); err != nil { return err } } gpIdJson, hasGpId := e.ext["gpid"] - if hasGpId { - if err := json.Unmarshal(gpIdJson, &e.gpId); err != nil { + if hasGpId && gpIdJson != nil { + if err := jsonutil.Unmarshal(gpIdJson, &e.gpId); err != nil { return err } } @@ -1590,7 +1613,7 @@ func (e *ImpExt) unmarshal(extJson json.RawMessage) error { func (e *ImpExt) marshal() (json.RawMessage, error) { if e.prebidDirty { if e.prebid != nil { - prebidJson, err := json.Marshal(e.prebid) + prebidJson, err := jsonutil.Marshal(e.prebid) if err != nil { return nil, err } @@ -1607,7 +1630,7 @@ func (e *ImpExt) marshal() (json.RawMessage, error) { if e.tidDirty { if len(e.tid) > 0 { - tidJson, err := json.Marshal(e.tid) + tidJson, err := jsonutil.Marshal(e.tid) if err != nil { return nil, err } @@ -1622,7 +1645,7 @@ func (e *ImpExt) marshal() (json.RawMessage, error) { if len(e.ext) == 0 { return nil, nil } - return json.Marshal(e.ext) + return jsonutil.Marshal(e.ext) } func (e *ImpExt) Dirty() bool { diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go index afa047e6909..0127c545274 100644 --- a/openrtb_ext/request_wrapper_test.go +++ b/openrtb_ext/request_wrapper_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -1792,10 +1793,10 @@ func TestImpWrapperGetImpExt(t *testing.T) { var isRewardedInventoryOne int8 = 1 testCases := []struct { - description string - givenWrapper ImpWrapper - expectedImpExt ImpExt - expectedError string + description string + givenWrapper ImpWrapper + expectedImpExt ImpExt + expectedErrorType error }{ { description: "Empty", @@ -1833,21 +1834,21 @@ func TestImpWrapperGetImpExt(t *testing.T) { expectedImpExt: ImpExt{ext: map[string]json.RawMessage{"foo": json.RawMessage("bar")}}, }, { - description: "Error - Ext", - givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`malformed`)}}, - expectedError: "invalid character 'm' looking for beginning of value", + description: "Error - Ext", + givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`malformed`)}}, + expectedErrorType: &errortypes.FailedToUnmarshal{}, }, { - description: "Error - Ext - Prebid", - givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"prebid":malformed}`)}}, - expectedError: "invalid character 'm' looking for beginning of value", + description: "Error - Ext - Prebid", + givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"prebid":malformed}`)}}, + expectedErrorType: &errortypes.FailedToUnmarshal{}, }, } for _, test := range testCases { impExt, err := test.givenWrapper.GetImpExt() - if test.expectedError != "" { - assert.EqualError(t, err, test.expectedError, test.description) + if test.expectedErrorType != nil { + assert.IsType(t, test.expectedErrorType, err) } else { assert.NoError(t, err, test.description) assert.Equal(t, test.expectedImpExt, *impExt, test.description) diff --git a/openrtb_ext/site_test.go b/openrtb_ext/site_test.go index 67ec6cc4f99..7a7140282f2 100644 --- a/openrtb_ext/site_test.go +++ b/openrtb_ext/site_test.go @@ -1,28 +1,28 @@ package openrtb_ext_test import ( - "encoding/json" "testing" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) func TestInvalidSiteExt(t *testing.T) { var s openrtb_ext.ExtSite - assert.EqualError(t, json.Unmarshal([]byte(`{"amp":-1}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") - assert.EqualError(t, json.Unmarshal([]byte(`{"amp":2}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") - assert.EqualError(t, json.Unmarshal([]byte(`{"amp":true}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") - assert.EqualError(t, json.Unmarshal([]byte(`{"amp":null}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") - assert.EqualError(t, json.Unmarshal([]byte(`{"amp":"1"}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"amp":-1}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"amp":2}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"amp":true}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"amp":null}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"amp":"1"}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") } func TestValidSiteExt(t *testing.T) { var s openrtb_ext.ExtSite - assert.NoError(t, json.Unmarshal([]byte(`{"amp":0}`), &s)) + assert.NoError(t, jsonutil.UnmarshalValid([]byte(`{"amp":0}`), &s)) assert.EqualValues(t, 0, s.AMP) - assert.NoError(t, json.Unmarshal([]byte(`{"amp":1}`), &s)) + assert.NoError(t, jsonutil.UnmarshalValid([]byte(`{"amp":1}`), &s)) assert.EqualValues(t, 1, s.AMP) - assert.NoError(t, json.Unmarshal([]byte(`{"amp": 1 }`), &s)) + assert.NoError(t, jsonutil.UnmarshalValid([]byte(`{"amp": 1 }`), &s)) assert.EqualValues(t, 1, s.AMP) } diff --git a/ortb/default_test.go b/ortb/default_test.go index 04eeeebdcb6..8bda02ef4f5 100644 --- a/ortb/default_test.go +++ b/ortb/default_test.go @@ -8,7 +8,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/ptrutil" ) @@ -31,7 +33,7 @@ func TestSetDefaults(t *testing.T) { name: "malformed request.ext", givenRequest: openrtb2.BidRequest{Ext: json.RawMessage(`malformed`)}, expectedRequest: openrtb2.BidRequest{Ext: json.RawMessage(`malformed`)}, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErr: "expect { or n, but found m", }, { name: "targeting", // tests integration with setDefaultsTargeting @@ -55,6 +57,7 @@ func TestSetDefaults(t *testing.T) { // assert error if len(test.expectedErr) > 0 { assert.EqualError(t, err, test.expectedErr, "Error") + assert.IsType(t, &errortypes.FailedToUnmarshal{}, err) } // rebuild request @@ -66,10 +69,10 @@ func TestSetDefaults(t *testing.T) { assert.Equal(t, &test.expectedRequest, wrapper.BidRequest, "Request") } else { // assert request as json to ignore order in ext fields - expectedRequestJSON, err := json.Marshal(test.expectedRequest) + expectedRequestJSON, err := jsonutil.Marshal(test.expectedRequest) require.NoError(t, err, "Marshal Expected Request") - actualRequestJSON, err := json.Marshal(wrapper.BidRequest) + actualRequestJSON, err := jsonutil.Marshal(wrapper.BidRequest) require.NoError(t, err, "Marshal Actual Request") assert.JSONEq(t, string(expectedRequestJSON), string(actualRequestJSON), "Request") diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index ec390364849..f20d3c7829f 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -13,6 +13,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/metrics" metricsConf "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -297,7 +298,7 @@ func newHandler(numResponses int) http.HandlerFunc { resp.Responses[i].UUID = strconv.Itoa(i) } - respBytes, _ := json.Marshal(resp) + respBytes, _ := jsonutil.Marshal(resp) w.Write(respBytes) }) } diff --git a/privacy/scrubber.go b/privacy/scrubber.go index 59b74a2532b..0cfc7cdd7f4 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -6,6 +6,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/ptrutil" "github.com/prebid/openrtb/v19/openrtb2" @@ -103,7 +104,7 @@ func (s scrubber) ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enfo if userCopy != nil && (enforcement.UFPD || enforcement.Eids) { if len(userCopy.Ext) != 0 { - json.Unmarshal(userCopy.Ext, &userExtParsed) + jsonutil.Unmarshal(userCopy.Ext, &userExtParsed) } } @@ -148,7 +149,7 @@ func (s scrubber) ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enfo } if userExtModified { - userExt, _ := json.Marshal(userExtParsed) + userExt, _ := jsonutil.Marshal(userExtParsed) userCopy.Ext = userExt } @@ -283,7 +284,7 @@ func scrubExtIDs(ext json.RawMessage, fieldName string) json.RawMessage { } var userExtParsed map[string]json.RawMessage - err := json.Unmarshal(ext, &userExtParsed) + err := jsonutil.Unmarshal(ext, &userExtParsed) if err != nil { return ext } @@ -291,7 +292,7 @@ func scrubExtIDs(ext json.RawMessage, fieldName string) json.RawMessage { _, hasField := userExtParsed[fieldName] if hasField { delete(userExtParsed, fieldName) - result, err := json.Marshal(userExtParsed) + result, err := jsonutil.Marshal(userExtParsed) if err == nil { return result } diff --git a/router/router.go b/router/router.go index 29bd8382e78..70b7860d661 100644 --- a/router/router.go +++ b/router/router.go @@ -34,6 +34,7 @@ import ( "github.com/prebid/prebid-server/server/ssl" storedRequestsConf "github.com/prebid/prebid-server/stored_requests/config" "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/uuidutil" "github.com/prebid/prebid-server/version" @@ -91,7 +92,7 @@ func newJsonDirectoryServer(schemaDirectory string, validator openrtb_ext.Bidder data[aliasName] = bidderData } - response, err := json.Marshal(data) + response, err := jsonutil.Marshal(data) if err != nil { glog.Fatalf("Failed to marshal bidder param JSON-schema: %v", err) } @@ -358,7 +359,7 @@ func readDefaultRequest(defReqConfig config.DefReqConfig) (map[string]string, [] return aliases, []byte{} } - if err := json.Unmarshal(defReqJSON, defReq); err != nil { + if err := jsonutil.UnmarshalValid(defReqJSON, defReq); err != nil { // we might not have aliases defined, but will atleast show that the JSON file is parsable. glog.Fatalf("error parsing alias json in file %s: %v", defReqConfig.FileSystem.FileName, err) return aliases, []byte{} diff --git a/router/router_test.go b/router/router_test.go index 41c2a724c91..f4f7715e6c3 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -9,6 +9,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -45,7 +46,7 @@ func TestNewJsonDirectoryServer(t *testing.T) { handler(recorder, request, nil) var data map[string]json.RawMessage - json.Unmarshal(recorder.Body.Bytes(), &data) + jsonutil.UnmarshalValid(recorder.Body.Bytes(), &data) // Make sure that every adapter has a json schema by the same name associated with it. adapterFiles, err := os.ReadDir(adapterDirectory) diff --git a/schain/schainwriter.go b/schain/schainwriter.go index 7e2161adb3b..e7c9dd4ce72 100644 --- a/schain/schainwriter.go +++ b/schain/schainwriter.go @@ -1,10 +1,9 @@ package schain import ( - "encoding/json" - "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" ) // NewSChainWriter creates an ORTB 2.5 schain writer instance @@ -70,7 +69,7 @@ func (w SChainWriter) Write(req *openrtb2.BidRequest, bidder string) { schain.SChain.Nodes = append(schain.SChain.Nodes, *w.hostSChainNode) } - sourceExt, err := json.Marshal(schain) + sourceExt, err := jsonutil.Marshal(schain) if err == nil { req.Source.Ext = sourceExt } diff --git a/schain/schainwriter_test.go b/schain/schainwriter_test.go index e98c962b4fa..9288b531d56 100644 --- a/schain/schainwriter_test.go +++ b/schain/schainwriter_test.go @@ -6,6 +6,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -197,7 +198,7 @@ func TestSChainWriter(t *testing.T) { var reqExt *openrtb_ext.ExtRequest if tt.giveRequest.Ext != nil { reqExt = &openrtb_ext.ExtRequest{} - err := json.Unmarshal(tt.giveRequest.Ext, reqExt) + err := jsonutil.UnmarshalValid(tt.giveRequest.Ext, reqExt) if err != nil { t.Error("Unable to unmarshal request.ext") } diff --git a/stored_requests/backends/file_fetcher/fetcher.go b/stored_requests/backends/file_fetcher/fetcher.go index 56f5bdf853c..a9bbe919dcf 100644 --- a/stored_requests/backends/file_fetcher/fetcher.go +++ b/stored_requests/backends/file_fetcher/fetcher.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) @@ -78,7 +79,7 @@ func (fetcher *eagerFetcher) FetchCategories(ctx context.Context, primaryAdServe tmp := make(map[string]stored_requests.Category) - if err := json.Unmarshal(file, &tmp); err != nil { + if err := jsonutil.UnmarshalValid(file, &tmp); err != nil { return "", fmt.Errorf("Unable to unmarshal categories for adserver: '%s', publisherId: '%s'", primaryAdServer, publisherId) } fetcher.Categories[fileName] = tmp diff --git a/stored_requests/backends/file_fetcher/fetcher_test.go b/stored_requests/backends/file_fetcher/fetcher_test.go index 3c585f9f456..a69945641f1 100644 --- a/stored_requests/backends/file_fetcher/fetcher_test.go +++ b/stored_requests/backends/file_fetcher/fetcher_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -58,7 +59,7 @@ func validateStoredReqOne(t *testing.T, storedRequests map[string]json.RawMessag } var req1Val map[string]string - if err := json.Unmarshal(value, &req1Val); err != nil { + if err := jsonutil.UnmarshalValid(value, &req1Val); err != nil { t.Errorf("Failed to unmarshal 1: %v", err) } if len(req1Val) != 1 { @@ -80,7 +81,7 @@ func validateStoredReqTwo(t *testing.T, storedRequests map[string]json.RawMessag } var req2Val string - if err := json.Unmarshal(value, &req2Val); err != nil { + if err := jsonutil.UnmarshalValid(value, &req2Val); err != nil { t.Errorf("Failed to unmarshal %d: %v", 2, err) } if req2Val != `esca"ped` { @@ -95,7 +96,7 @@ func validateImp(t *testing.T, storedImps map[string]json.RawMessage) { } var impVal map[string]bool - if err := json.Unmarshal(value, &impVal); err != nil { + if err := jsonutil.UnmarshalValid(value, &impVal); err != nil { t.Errorf("Failed to unmarshal some-imp: %v", err) } if len(impVal) != 1 { diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index 326b63fce71..88afa39fb1d 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -1,7 +1,6 @@ package http_fetcher import ( - "bytes" "context" "encoding/json" "fmt" @@ -11,6 +10,7 @@ import ( "strings" "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" "github.com/golang/glog" @@ -142,7 +142,7 @@ func (fetcher *HttpFetcher) FetchAccounts(ctx context.Context, accountIDs []stri } } var responseData accountsResponseContract - if err = json.Unmarshal(respBytes, &responseData); err != nil { + if err = jsonutil.UnmarshalValid(respBytes, &responseData); err != nil { return nil, []error{ fmt.Errorf(`Error fetching accounts %v via http: failed to parse response: %v`, accountIDs, err), } @@ -209,7 +209,7 @@ func (fetcher *HttpFetcher) FetchCategories(ctx context.Context, primaryAdServer respBytes, err := io.ReadAll(httpResp.Body) tmp := make(map[string]stored_requests.Category) - if err := json.Unmarshal(respBytes, &tmp); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, &tmp); err != nil { return "", fmt.Errorf("Unable to unmarshal categories for adserver: '%s', publisherId: '%s'", primaryAdServer, publisherId) } fetcher.Categories[dataName] = tmp @@ -240,7 +240,7 @@ func unpackResponse(resp *http.Response) (requestData map[string]json.RawMessage if resp.StatusCode == http.StatusOK { var responseObj responseContract - if err := json.Unmarshal(respBytes, &responseObj); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, &responseObj); err != nil { errs = append(errs, err) return } @@ -260,7 +260,7 @@ func unpackResponse(resp *http.Response) (requestData map[string]json.RawMessage func convertNullsToErrs(m map[string]json.RawMessage, dataType string, errs []error) []error { for id, val := range m { - if bytes.Equal(val, []byte("null")) { + if val == nil { delete(m, id) errs = append(errs, stored_requests.NotFoundError{ ID: id, diff --git a/stored_requests/backends/http_fetcher/fetcher_test.go b/stored_requests/backends/http_fetcher/fetcher_test.go index 3c8ca7cb070..80be6918ad8 100644 --- a/stored_requests/backends/http_fetcher/fetcher_test.go +++ b/stored_requests/backends/http_fetcher/fetcher_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -233,7 +234,7 @@ func newHandler(t *testing.T, expectReqIDs []string, expectImpIDs []string, json Imps: impIDResponse, } - if respBytes, err := json.Marshal(respObj); err != nil { + if respBytes, err := jsonutil.Marshal(respObj); err != nil { t.Errorf("failed to marshal responseContract in test: %v", err) w.WriteHeader(http.StatusInternalServerError) } else { @@ -267,7 +268,7 @@ func newAccountHandler(t *testing.T, expectAccIDs []string) func(w http.Response Accounts: accIDResponse, } - if respBytes, err := json.Marshal(respObj); err != nil { + if respBytes, err := jsonutil.Marshal(respObj); err != nil { t.Errorf("failed to marshal responseContract in test: %v", err) w.WriteHeader(http.StatusInternalServerError) } else { @@ -327,7 +328,7 @@ func richSplit(queryVal string) []string { } func jsonifyID(id string) json.RawMessage { - if b, err := json.Marshal(id); err != nil { + if b, err := jsonutil.Marshal(id); err != nil { return json.RawMessage([]byte("\"error encoding ID=" + id + "\"")) } else { return json.RawMessage(b) diff --git a/stored_requests/events/api/api.go b/stored_requests/events/api/api.go index bf8edd2d849..30778f0e11a 100644 --- a/stored_requests/events/api/api.go +++ b/stored_requests/events/api/api.go @@ -1,12 +1,12 @@ package api import ( - "encoding/json" "io" "net/http" "github.com/julienschmidt/httprouter" "github.com/prebid/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/util/jsonutil" ) type eventsAPI struct { @@ -43,7 +43,7 @@ func (api *eventsAPI) HandleEvent(w http.ResponseWriter, r *http.Request, _ http } var save events.Save - if err := json.Unmarshal(body, &save); err != nil { + if err := jsonutil.UnmarshalValid(body, &save); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Invalid update.\n")) return @@ -59,7 +59,7 @@ func (api *eventsAPI) HandleEvent(w http.ResponseWriter, r *http.Request, _ http } var invalidation events.Invalidation - if err := json.Unmarshal(body, &invalidation); err != nil { + if err := jsonutil.UnmarshalValid(body, &invalidation); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Invalid invalidation.\n")) return diff --git a/stored_requests/events/http/http.go b/stored_requests/events/http/http.go index 1c4f8fdff73..be5b85a03a6 100644 --- a/stored_requests/events/http/http.go +++ b/stored_requests/events/http/http.go @@ -13,6 +13,7 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/golang/glog" ) @@ -184,7 +185,7 @@ func (e *HTTPEvents) parse(endpoint string, resp *httpCore.Response, err error) } var respObj responseContract - if err := json.Unmarshal(respBytes, &respObj); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, &respObj); err != nil { glog.Errorf("Failed to unmarshal body of GET %s for Stored Requests: %v", endpoint, err) return nil, false } diff --git a/stored_requests/events/http/http_test.go b/stored_requests/events/http/http_test.go index a185a3c2360..663d8cd193f 100644 --- a/stored_requests/events/http/http_test.go +++ b/stored_requests/events/http/http_test.go @@ -2,13 +2,13 @@ package http import ( "context" - "encoding/json" "fmt" httpCore "net/http" "net/http/httptest" "testing" "time" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -151,14 +151,14 @@ func TestStartup(t *testing.T) { t.Run(fmt.Sprintf("Step %d", i+1), func(t *testing.T) { // Check expected Saves if len(test.saves) > 0 { - saves, err := json.Marshal(<-ev.Saves()) + saves, err := jsonutil.Marshal(<-ev.Saves()) assert.NoError(t, err, `Failed to marshal event.Save object: %v`, err) assert.JSONEq(t, test.saves, string(saves)) } assert.Empty(t, ev.Saves(), "Unexpected additional messages in save channel") // Check expected Invalidations if len(test.invalidations) > 0 { - invalidations, err := json.Marshal(<-ev.Invalidations()) + invalidations, err := jsonutil.Marshal(<-ev.Invalidations()) assert.NoError(t, err, `Failed to marshal event.Invalidation object: %v`, err) assert.JSONEq(t, test.invalidations, string(invalidations)) } diff --git a/stored_responses/stored_responses.go b/stored_responses/stored_responses.go index 6a5da01deba..ef8fe50619d 100644 --- a/stored_responses/stored_responses.go +++ b/stored_responses/stored_responses.go @@ -9,6 +9,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/util/jsonutil" ) type ImpsWithAuctionResponseIDs map[string]string @@ -210,7 +211,7 @@ func parseImpInfo(requestJson []byte) (impData []ImpExtPrebidData, errs []error) impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid") var impExtPrebid openrtb_ext.ExtImpPrebid if impExtData != nil { - if err := json.Unmarshal(impExtData, &impExtPrebid); err != nil { + if err := jsonutil.Unmarshal(impExtData, &impExtPrebid); err != nil { errs = append(errs, err) } } diff --git a/usersync/cookie.go b/usersync/cookie.go index 94ada94ed75..88524018c49 100644 --- a/usersync/cookie.go +++ b/usersync/cookie.go @@ -1,13 +1,13 @@ package usersync import ( - "encoding/json" "errors" "net/http" "time" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" ) const uidCookieName = "uids" @@ -226,7 +226,7 @@ type cookieJson struct { } func (cookie *Cookie) MarshalJSON() ([]byte, error) { - return json.Marshal(cookieJson{ + return jsonutil.Marshal(cookieJson{ UIDs: cookie.uids, OptOut: cookie.optOut, }) @@ -234,7 +234,7 @@ func (cookie *Cookie) MarshalJSON() ([]byte, error) { func (cookie *Cookie) UnmarshalJSON(b []byte) error { var cookieContract cookieJson - if err := json.Unmarshal(b, &cookieContract); err != nil { + if err := jsonutil.Unmarshal(b, &cookieContract); err != nil { return err } diff --git a/usersync/decoder.go b/usersync/decoder.go index 3ff13aa3242..c803fbe2a52 100644 --- a/usersync/decoder.go +++ b/usersync/decoder.go @@ -2,12 +2,12 @@ package usersync import ( "encoding/base64" - "encoding/json" + + "github.com/prebid/prebid-server/util/jsonutil" ) type Decoder interface { - // Decode takes an encoded string and decodes it into a cookie - Decode(v string) *Cookie + Decode(encodedValue string) *Cookie } type Base64Decoder struct{} @@ -19,7 +19,7 @@ func (d Base64Decoder) Decode(encodedValue string) *Cookie { } var cookie Cookie - if err = json.Unmarshal(jsonValue, &cookie); err != nil { + if err = jsonutil.UnmarshalValid(jsonValue, &cookie); err != nil { return NewCookie() } diff --git a/usersync/encoder.go b/usersync/encoder.go index eef7e2ef34f..74472f23307 100644 --- a/usersync/encoder.go +++ b/usersync/encoder.go @@ -2,7 +2,8 @@ package usersync import ( "encoding/base64" - "encoding/json" + + "github.com/prebid/prebid-server/util/jsonutil" ) type Encoder interface { @@ -13,7 +14,7 @@ type Encoder interface { type Base64Encoder struct{} func (e Base64Encoder) Encode(c *Cookie) (string, error) { - j, err := json.Marshal(c) + j, err := jsonutil.Marshal(c) if err != nil { return "", err } diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go index 3b468731cad..a8981477dc6 100644 --- a/util/jsonutil/jsonutil.go +++ b/util/jsonutil/jsonutil.go @@ -4,14 +4,16 @@ import ( "bytes" "encoding/json" "io" + "strings" + + jsoniter "github.com/json-iterator/go" + "github.com/prebid/prebid-server/errortypes" ) -var comma = []byte(",")[0] -var colon = []byte(":")[0] -var sqBracket = []byte("]")[0] -var openCurlyBracket = []byte("{")[0] -var closingCurlyBracket = []byte("}")[0] -var quote = []byte(`"`)[0] +var comma = byte(',') +var colon = byte(':') +var sqBracket = byte(']') +var closingCurlyBracket = byte('}') // Finds element in json byte array with any level of nesting func FindElement(extension []byte, elementNames ...string) (bool, int64, int64, error) { @@ -110,3 +112,102 @@ func DropElement(extension []byte, elementNames ...string) ([]byte, error) { } return extension, nil } + +// jsonConfigValidationOn attempts to maintain compatibility with the standard library which +// includes enabling validation +var jsonConfigValidationOn = jsoniter.ConfigCompatibleWithStandardLibrary + +// jsonConfigValidationOff disables validation +var jsonConfigValidationOff = jsoniter.Config{ + EscapeHTML: true, + SortMapKeys: true, + ValidateJsonRawMessage: false, +}.Froze() + +// Unmarshal unmarshals a byte slice into the specified data structure without performing +// any validation on the data. An unmarshal error is returned if a non-validation error occurs. +func Unmarshal(data []byte, v interface{}) error { + err := jsonConfigValidationOff.Unmarshal(data, v) + if err != nil { + return &errortypes.FailedToUnmarshal{ + Message: tryExtractErrorMessage(err), + } + } + return nil +} + +// UnmarshalValid validates and unmarshals a byte slice into the specified data structure +// returning an error if validation fails +func UnmarshalValid(data []byte, v interface{}) error { + if err := jsonConfigValidationOn.Unmarshal(data, v); err != nil { + return &errortypes.FailedToUnmarshal{ + Message: tryExtractErrorMessage(err), + } + } + return nil +} + +// Marshal marshals a data structure into a byte slice without performing any validation +// on the data. A marshal error is returned if a non-validation error occurs. +func Marshal(v interface{}) ([]byte, error) { + data, err := jsonConfigValidationOn.Marshal(v) + if err != nil { + return nil, &errortypes.FailedToMarshal{ + Message: err.Error(), + } + } + return data, nil +} + +// tryExtractErrorMessage attempts to extract a sane error message from the json-iter package. The errors +// returned from that library are not types and include a lot of extra information we don't want to respond with. +// This is hacky, but it's the only downside to the json-iter library. +func tryExtractErrorMessage(err error) string { + msg := err.Error() + + msgEndIndex := strings.LastIndex(msg, ", error found in #") + if msgEndIndex == -1 { + return msg + } + + msgStartIndex := strings.Index(msg, ": ") + if msgStartIndex == -1 { + return msg + } + + operationStack := []string{msg[0:msgStartIndex]} + for { + msgStartIndexNext := strings.Index(msg[msgStartIndex+2:], ": ") + + // no more matches + if msgStartIndexNext == -1 { + break + } + + // matches occur after the end message marker (sanity check) + if (msgStartIndex + msgStartIndexNext) >= msgEndIndex { + break + } + + // match should not contain a space, indicates operation is really an error message + match := msg[msgStartIndex+2 : msgStartIndex+2+msgStartIndexNext] + if strings.Contains(match, " ") { + break + } + + operationStack = append(operationStack, match) + msgStartIndex += msgStartIndexNext + 2 + } + + if len(operationStack) > 1 && isLikelyDetailedErrorMessage(msg[msgStartIndex+2:]) { + return "cannot unmarshal " + operationStack[len(operationStack)-2] + ": " + msg[msgStartIndex+2:msgEndIndex] + } + + return msg[msgStartIndex+2 : msgEndIndex] +} + +// isLikelyDetailedErrorMessage checks if the json unmarshal error contains enough information such +// that the caller clearly understands the context, where the structure name is not needed. +func isLikelyDetailedErrorMessage(msg string) bool { + return !strings.HasPrefix(msg, "request.") +} diff --git a/util/jsonutil/jsonutil_test.go b/util/jsonutil/jsonutil_test.go index 12b1fd5e803..09fb6727309 100644 --- a/util/jsonutil/jsonutil_test.go +++ b/util/jsonutil/jsonutil_test.go @@ -1,13 +1,14 @@ package jsonutil import ( - "github.com/stretchr/testify/assert" + "errors" "strings" "testing" + + "github.com/stretchr/testify/assert" ) func TestDropElement(t *testing.T) { - tests := []struct { description string input []byte @@ -183,3 +184,59 @@ func TestDropElement(t *testing.T) { } } } + +func TestTryExtractErrorMessage(t *testing.T) { + tests := []struct { + name string + givenErr string + expectedMsg string + }{ + { + name: "level-1", + givenErr: "readObjectStart: expect { or n, but found m, error found in #1 byte of ...|malformed|..., bigger context ...|malformed|..", + expectedMsg: "expect { or n, but found m", + }, + { + name: "level-2", + givenErr: "openrtb_ext.ExtRequestPrebidCache.Bids: readObjectStart: expect { or n, but found t, error found in #10 byte of ...|:{\"bids\":true}}|..., bigger context ...|{\"cache\":{\"bids\":true}}|...", + expectedMsg: "cannot unmarshal openrtb_ext.ExtRequestPrebidCache.Bids: expect { or n, but found t", + }, + { + name: "level-3+", + givenErr: "openrtb_ext.ExtRequestPrebid.Cache: openrtb_ext.ExtRequestPrebidCache.Bids: readObjectStart: expect { or n, but found t, error found in #10 byte of ...|:{\"bids\":true}}|..., bigger context ...|{\"cache\":{\"bids\":true}}|...", + expectedMsg: "cannot unmarshal openrtb_ext.ExtRequestPrebidCache.Bids: expect { or n, but found t", + }, + { + name: "error-msg", + givenErr: "Skip: do not know how to skip: 109, error found in #10 byte of ...|prebid\": malformed}|..., bigger context ...|{\"prebid\": malformed}|...", + expectedMsg: "do not know how to skip: 109", + }, + { + name: "specific", + givenErr: "openrtb_ext.ExtDevicePrebid.Interstitial: unmarshalerDecoder: request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100, error found in #10 byte of ...| }\n }|..., bigger context ...|: 120,\n \"minheightperc\": 60\n }\n }|...", + expectedMsg: "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100", + }, + { + name: "normal", + givenErr: "normal error message", + expectedMsg: "normal error message", + }, + { + name: "norma-false-start", + givenErr: "false: normal error message", + expectedMsg: "false: normal error message", + }, + { + name: "norma-false-end", + givenErr: "normal error message, error found in #10 but doesn't follow format", + expectedMsg: "normal error message, error found in #10 but doesn't follow format", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := tryExtractErrorMessage(errors.New(test.givenErr)) + assert.Equal(t, test.expectedMsg, result) + }) + } +} diff --git a/util/jsonutil/stringInt_test.go b/util/jsonutil/stringInt_test.go index e8639c7acee..cd2f3476e46 100644 --- a/util/jsonutil/stringInt_test.go +++ b/util/jsonutil/stringInt_test.go @@ -1,7 +1,6 @@ package jsonutil import ( - "encoding/json" "testing" "github.com/buger/jsonparser" @@ -16,27 +15,27 @@ func TestStringIntUnmarshalJSON(t *testing.T) { t.Run("string", func(t *testing.T) { jsonData := []byte(`{"item_id":"30"}`) var item Item - assert.NoError(t, json.Unmarshal(jsonData, &item)) + assert.NoError(t, UnmarshalValid(jsonData, &item)) assert.Equal(t, 30, int(item.ItemId)) }) t.Run("int", func(t *testing.T) { jsonData := []byte(`{"item_id":30}`) var item Item - assert.NoError(t, json.Unmarshal(jsonData, &item)) + assert.NoError(t, UnmarshalValid(jsonData, &item)) assert.Equal(t, 30, int(item.ItemId)) }) t.Run("empty_id", func(t *testing.T) { jsonData := []byte(`{"item_id": ""}`) var item Item - assert.NoError(t, json.Unmarshal(jsonData, &item)) + assert.NoError(t, UnmarshalValid(jsonData, &item)) }) t.Run("invalid_input", func(t *testing.T) { jsonData := []byte(`{"item_id":true}`) var item Item - err := json.Unmarshal(jsonData, &item) - assert.Equal(t, jsonparser.MalformedValueError, err) + err := UnmarshalValid(jsonData, &item) + assert.EqualError(t, err, "cannot unmarshal jsonutil.Item.ItemId: "+jsonparser.MalformedValueError.Error()) }) } diff --git a/version/xprebidheader_test.go b/version/xprebidheader_test.go index 90aef6cb420..a1c7b355bb8 100644 --- a/version/xprebidheader_test.go +++ b/version/xprebidheader_test.go @@ -1,13 +1,13 @@ package version import ( - "encoding/json" "testing" "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/assert" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" ) func TestBuildXPrebidHeader(t *testing.T) { @@ -134,12 +134,12 @@ func TestBuildXPrebidHeaderForRequest(t *testing.T) { for _, test := range testCases { req := &openrtb2.BidRequest{} if test.requestExt != nil { - reqExt, err := json.Marshal(test.requestExt) + reqExt, err := jsonutil.Marshal(test.requestExt) assert.NoError(t, err, test.description+":err marshalling reqExt") req.Ext = reqExt } if test.requestAppExt != nil { - reqAppExt, err := json.Marshal(test.requestAppExt) + reqAppExt, err := jsonutil.Marshal(test.requestAppExt) assert.NoError(t, err, test.description+":err marshalling reqAppExt") req.App = &openrtb2.App{Ext: reqAppExt} } From cd03a0f25ff0e5e4f8d5bc91429ad243e27cd6a8 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 19 Oct 2023 15:56:09 -0400 Subject: [PATCH 071/138] Restore Alias User Syncs (#3244) --- static/bidder-info/adform.yaml | 4 ++++ static/bidder-info/quantumdex.yaml | 9 ++++++++- static/bidder-info/valueimpression.yaml | 9 ++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/static/bidder-info/adform.yaml b/static/bidder-info/adform.yaml index 4aa6e70f1d0..2ad892785ce 100644 --- a/static/bidder-info/adform.yaml +++ b/static/bidder-info/adform.yaml @@ -1 +1,5 @@ aliasOf: adf +userSync: + redirect: + url: "https://cm.adform.net/cookie?redirect_url={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/quantumdex.yaml b/static/bidder-info/quantumdex.yaml index 32043ad62fb..fae2a987dd7 100644 --- a/static/bidder-info/quantumdex.yaml +++ b/static/bidder-info/quantumdex.yaml @@ -1 +1,8 @@ -aliasOf: "apacdex" \ No newline at end of file +aliasOf: "apacdex" +userSync: + iframe: + url: https://sync.quantumdex.io/usersync/pbs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}} + userMacro: "[UID]" + redirect: + url: "https://sync.quantumdex.io/getuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/valueimpression.yaml b/static/bidder-info/valueimpression.yaml index 32043ad62fb..fae2a987dd7 100644 --- a/static/bidder-info/valueimpression.yaml +++ b/static/bidder-info/valueimpression.yaml @@ -1 +1,8 @@ -aliasOf: "apacdex" \ No newline at end of file +aliasOf: "apacdex" +userSync: + iframe: + url: https://sync.quantumdex.io/usersync/pbs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}} + userMacro: "[UID]" + redirect: + url: "https://sync.quantumdex.io/getuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "[UID]" From 50ad642976f3de1e06a310eb87dc694de866123a Mon Sep 17 00:00:00 2001 From: dennisjay Date: Fri, 20 Oct 2023 00:13:29 +0200 Subject: [PATCH 072/138] Revert "Remove Adapter: Define Media" (#3242) --- adapters/definemedia/definemedia.go | 109 +++++++ adapters/definemedia/definemedia_test.go | 21 ++ .../exemplary/sample-conative-banner.json | 270 ++++++++++++++++++ .../exemplary/sample-conative-native.json | 257 +++++++++++++++++ .../supplemental/nobid-response.json | 222 ++++++++++++++ .../supplemental/nocontent-response.json | 219 ++++++++++++++ .../supplemental/status_400.json | 224 +++++++++++++++ .../supplemental/status_418.json | 224 +++++++++++++++ .../supplemental/unmarshal-error.json | 224 +++++++++++++++ .../supplemental/unsupported-type.json | 250 ++++++++++++++++ adapters/definemedia/params_test.go | 48 ++++ exchange/adapter_builders.go | 2 + exchange/adapter_util.go | 1 - openrtb_ext/bidders.go | 2 + openrtb_ext/imp_definemedia.go | 6 + static/bidder-info/definemedia.yaml | 10 + static/bidder-params/definemedia.json | 19 ++ 17 files changed, 2107 insertions(+), 1 deletion(-) create mode 100644 adapters/definemedia/definemedia.go create mode 100644 adapters/definemedia/definemedia_test.go create mode 100644 adapters/definemedia/definemediatest/exemplary/sample-conative-banner.json create mode 100644 adapters/definemedia/definemediatest/exemplary/sample-conative-native.json create mode 100644 adapters/definemedia/definemediatest/supplemental/nobid-response.json create mode 100644 adapters/definemedia/definemediatest/supplemental/nocontent-response.json create mode 100644 adapters/definemedia/definemediatest/supplemental/status_400.json create mode 100644 adapters/definemedia/definemediatest/supplemental/status_418.json create mode 100644 adapters/definemedia/definemediatest/supplemental/unmarshal-error.json create mode 100644 adapters/definemedia/definemediatest/supplemental/unsupported-type.json create mode 100644 adapters/definemedia/params_test.go create mode 100644 openrtb_ext/imp_definemedia.go create mode 100644 static/bidder-info/definemedia.yaml create mode 100644 static/bidder-params/definemedia.json diff --git a/adapters/definemedia/definemedia.go b/adapters/definemedia/definemedia.go new file mode 100644 index 00000000000..3e014e3c16d --- /dev/null +++ b/adapters/definemedia/definemedia.go @@ -0,0 +1,109 @@ +package definemedia + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the Foo adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids. +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors []error + requestJSON, err := json.Marshal(request) + if err != nil { + errors = append(errors, err) + return nil, errors + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, errors + +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidsCapacity := 1 + if len(bidResp.SeatBid) > 0 { + bidsCapacity = len(bidResp.SeatBid[0].Bid) + } + bidResponse := adapters.NewBidderResponseWithBidsCapacity(bidsCapacity) + var errors []error + for _, sb := range bidResp.SeatBid { + for i, bid := range sb.Bid { + bidType, err := getMediaTypeForBid(bid) + if err != nil { + errors = append(errors, err) + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + }) + + } + } + + return bidResponse, errors +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + if bid.Ext != nil { + var bidExt openrtb_ext.ExtBid + err := json.Unmarshal(bid.Ext, &bidExt) + if err == nil && bidExt.Prebid != nil { + if (bidExt.Prebid.Type == openrtb_ext.BidTypeBanner) || (bidExt.Prebid.Type == openrtb_ext.BidTypeNative) { + return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)) + } + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Invalid mediatype in the impression"), + } + } + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to parse impression \"%s\" mediatype", bid.ImpID), + } +} diff --git a/adapters/definemedia/definemedia_test.go b/adapters/definemedia/definemedia_test.go new file mode 100644 index 00000000000..3ed0cb938b8 --- /dev/null +++ b/adapters/definemedia/definemedia_test.go @@ -0,0 +1,21 @@ +package definemedia + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderDefinemedia, config.Adapter{ + Endpoint: "https://rtb.conative.network/openrtb2/auction"}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "definemediatest", bidder) +} diff --git a/adapters/definemedia/definemediatest/exemplary/sample-conative-banner.json b/adapters/definemedia/definemediatest/exemplary/sample-conative-banner.json new file mode 100644 index 00000000000..a7d7e411f78 --- /dev/null +++ b/adapters/definemedia/definemediatest/exemplary/sample-conative-banner.json @@ -0,0 +1,270 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", + "prebid": { + "bidder": { + "definemedia": { + "mandantId": 12 + } + } + } + }, + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "topframe": 1, + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "domain": "localhost:8080", + "publisher": { + "domain": "localhost:8080", + "id": "1" + } + }, + "device": { + "w": 1098, + "h": 1169, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "language": "de", + "sua": { + "source": 2, + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "mobile": 0, + "model": "", + "bitness": "64", + "architecture": "x86" + } + }, + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "test": 0, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1671449004622, + "targeting": { + "includewinners": true, + "includebidderkeys": false + }, + "debug": true, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + } + } + }, + "tmax": 1000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://rtb.conative.network/openrtb2/auction", + "body": { + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "topframe": 1 + }, + "ext": { + "prebid": { + "bidder": { + "definemedia": { + "mandantId": 12 + } + } + }, + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" + } + } + ], + "site": { + "domain": "localhost:8080", + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "publisher": { + "id": "1", + "domain": "localhost:8080" + } + }, + "device": { + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "sua": { + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "mobile": 0, + "architecture": "x86", + "bitness": "64", + "source": 2 + }, + "h": 1169, + "w": 1098, + "language": "de" + }, + "tmax": 1000, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1.671449004622e+12, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + }, + "debug": true, + "targeting": { + "includebidderkeys": false, + "includewinners": true + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "46189656-7e2e-477d-b7f2-e05de224bb89", + "impid": "div-gpt-ad-1460505748561-0", + "price": 100, + "adm": "{banner html}", + "adomain": [ + "test.com" + ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "46189656-7e2e-477d-b7f2-e05de224bb89", + "impid": "div-gpt-ad-1460505748561-0", + "price": 100, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ + "test.com" + ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/definemedia/definemediatest/exemplary/sample-conative-native.json b/adapters/definemedia/definemediatest/exemplary/sample-conative-native.json new file mode 100644 index 00000000000..4fe56a4c22e --- /dev/null +++ b/adapters/definemedia/definemediatest/exemplary/sample-conative-native.json @@ -0,0 +1,257 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", + "prebid": { + "bidder": { + "definemedia": { + "mandantId": 12 + } + } + } + }, + "id": "div-gpt-ad-1460505748561-0", + "native": {} + } + ], + "site": { + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "domain": "localhost:8080", + "publisher": { + "domain": "localhost:8080", + "id": "1" + } + }, + "device": { + "w": 1098, + "h": 1169, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "language": "de", + "sua": { + "source": 2, + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "mobile": 0, + "model": "", + "bitness": "64", + "architecture": "x86" + } + }, + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "test": 0, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1671449004622, + "targeting": { + "includewinners": true, + "includebidderkeys": false + }, + "debug": true, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + } + } + }, + "tmax": 1000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://rtb.conative.network/openrtb2/auction", + "body": { + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "native": { + "request": "" + }, + "ext": { + "prebid": { + "bidder": { + "definemedia": { + "mandantId": 12 + } + } + }, + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" + } + } + ], + "site": { + "domain": "localhost:8080", + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "publisher": { + "id": "1", + "domain": "localhost:8080" + } + }, + "device": { + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "sua": { + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "mobile": 0, + "architecture": "x86", + "bitness": "64", + "source": 2 + }, + "h": 1169, + "w": 1098, + "language": "de" + }, + "tmax": 1000, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1.671449004622e+12, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + }, + "debug": true, + "targeting": { + "includebidderkeys": false, + "includewinners": true + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "46189656-7e2e-477d-b7f2-e05de224bb89", + "impid": "div-gpt-ad-1460505748561-0", + "price": 100, + "adm": "{\n \"ver\": \"1.1\",\n \"imptrackers\": [\"http://imptracker.com\"],\n \"jstracker\": \"\u003cscript\u003etrack()\u003c/script\u003e\",\n \"link\": {\n \"url\": \"http://i.am.a/URL\"\n },\n \"assets\": [\n {\n \"id\": 123,\n \"required\": 1,\n \"title\": {\n \"text\": \"Learn about this awesome thing\"\n }\n },\n {\n \"id\": 124,\n \"required\": 1,\n \"img\": {\n \"url\": \"http://www.myads.com/thumbnail1.png\"\n }\n },\n {\n \"id\": 128,\n \"required\": 1,\n \"img\": {\n \"url\": \"http://www.myads.com/largethumb1.png\"\n }\n },\n {\n \"id\": 126,\n \"required\": 1,\n \"data\": {\n \"value\": \"My Brand\"\n }\n },\n {\n \"id\": 127,\n \"required\": 1,\n \"data\": {\n \"value\": \"Learn all about this awesome story of someone using my product.\"\n }\n },\n {\n \"id\": 4,\n \"video\": {\n \"vasttag\": \"\u003cVAST version=\\\"2.0\\\"\u003e\u003c/VAST\u003e\"\n }\n },\n {\n \"id\": 5,\n \"link\": {\n \"url\": \"http://landing.com\",\n \"clicktrackers\": [\"http://tracker.com\"],\n \"fallback\": \"http://fallback.com\"\n }\n }\n ]\n}", + "adomain": [ + "test.com" + ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "native" + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "46189656-7e2e-477d-b7f2-e05de224bb89", + "impid": "div-gpt-ad-1460505748561-0", + "price": 100, + + "adm": "{\n \"ver\": \"1.1\",\n \"imptrackers\": [\"http://imptracker.com\"],\n \"jstracker\": \"\u003cscript\u003etrack()\u003c/script\u003e\",\n \"link\": {\n \"url\": \"http://i.am.a/URL\"\n },\n \"assets\": [\n {\n \"id\": 123,\n \"required\": 1,\n \"title\": {\n \"text\": \"Learn about this awesome thing\"\n }\n },\n {\n \"id\": 124,\n \"required\": 1,\n \"img\": {\n \"url\": \"http://www.myads.com/thumbnail1.png\"\n }\n },\n {\n \"id\": 128,\n \"required\": 1,\n \"img\": {\n \"url\": \"http://www.myads.com/largethumb1.png\"\n }\n },\n {\n \"id\": 126,\n \"required\": 1,\n \"data\": {\n \"value\": \"My Brand\"\n }\n },\n {\n \"id\": 127,\n \"required\": 1,\n \"data\": {\n \"value\": \"Learn all about this awesome story of someone using my product.\"\n }\n },\n {\n \"id\": 4,\n \"video\": {\n \"vasttag\": \"\u003cVAST version=\\\"2.0\\\"\u003e\u003c/VAST\u003e\"\n }\n },\n {\n \"id\": 5,\n \"link\": {\n \"url\": \"http://landing.com\",\n \"clicktrackers\": [\"http://tracker.com\"],\n \"fallback\": \"http://fallback.com\"\n }\n }\n ]\n}", + "crid": "test-creative-id", + "adomain": [ + "test.com" + ], + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/definemedia/definemediatest/supplemental/nobid-response.json b/adapters/definemedia/definemediatest/supplemental/nobid-response.json new file mode 100644 index 00000000000..aaa942da76e --- /dev/null +++ b/adapters/definemedia/definemediatest/supplemental/nobid-response.json @@ -0,0 +1,222 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", + "prebid": { + "bidder": { + "definemedia": {} + } + } + }, + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "topframe": 1, + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "domain": "localhost:8080", + "publisher": { + "domain": "localhost:8080", + "id": "1" + } + }, + "device": { + "w": 1098, + "h": 1169, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "language": "de", + "sua": { + "source": 2, + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "mobile": 0, + "model": "", + "bitness": "64", + "architecture": "x86" + } + }, + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "test": 0, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1671449004622, + "targeting": { + "includewinners": true, + "includebidderkeys": false + }, + "debug": true, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + } + } + }, + "tmax": 1000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://rtb.conative.network/openrtb2/auction", + "body": { + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "topframe": 1 + }, + "ext": { + "prebid": { + "bidder": { + "definemedia": {} + } + }, + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" + } + } + ], + "site": { + "domain": "localhost:8080", + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "publisher": { + "id": "1", + "domain": "localhost:8080" + } + }, + "device": { + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "sua": { + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "mobile": 0, + "architecture": "x86", + "bitness": "64", + "source": 2 + }, + "h": 1169, + "w": 1098, + "language": "de" + }, + "tmax": 1000, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1.671449004622e+12, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + }, + "debug": true, + "targeting": { + "includebidderkeys": false, + "includewinners": true + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": null, + "cur": null + } + } + } + ], + "expectedBidResponses": [{"currency":"USD","bids":[]}] + } diff --git a/adapters/definemedia/definemediatest/supplemental/nocontent-response.json b/adapters/definemedia/definemediatest/supplemental/nocontent-response.json new file mode 100644 index 00000000000..147d2185e1f --- /dev/null +++ b/adapters/definemedia/definemediatest/supplemental/nocontent-response.json @@ -0,0 +1,219 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", + "prebid": { + "bidder": { + "definemedia": {} + } + } + }, + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "topframe": 1, + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "domain": "localhost:8080", + "publisher": { + "domain": "localhost:8080", + "id": "1" + } + }, + "device": { + "w": 1098, + "h": 1169, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "language": "de", + "sua": { + "source": 2, + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "mobile": 0, + "model": "", + "bitness": "64", + "architecture": "x86" + } + }, + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "test": 0, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1671449004622, + "targeting": { + "includewinners": true, + "includebidderkeys": false + }, + "debug": true, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + } + } + }, + "tmax": 1000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://rtb.conative.network/openrtb2/auction", + "body": { + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "topframe": 1 + }, + "ext": { + "prebid": { + "bidder": { + "definemedia": {} + } + }, + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" + } + } + ], + "site": { + "domain": "localhost:8080", + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "publisher": { + "id": "1", + "domain": "localhost:8080" + } + }, + "device": { + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "sua": { + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "mobile": 0, + "architecture": "x86", + "bitness": "64", + "source": 2 + }, + "h": 1169, + "w": 1098, + "language": "de" + }, + "tmax": 1000, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1.671449004622e+12, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + }, + "debug": true, + "targeting": { + "includebidderkeys": false, + "includewinners": true + } + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] + } \ No newline at end of file diff --git a/adapters/definemedia/definemediatest/supplemental/status_400.json b/adapters/definemedia/definemediatest/supplemental/status_400.json new file mode 100644 index 00000000000..ec772bf8428 --- /dev/null +++ b/adapters/definemedia/definemediatest/supplemental/status_400.json @@ -0,0 +1,224 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", + "prebid": { + "bidder": { + "definemedia": {} + } + } + }, + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "topframe": 1, + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "domain": "localhost:8080", + "publisher": { + "domain": "localhost:8080", + "id": "1" + } + }, + "device": { + "w": 1098, + "h": 1169, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "language": "de", + "sua": { + "source": 2, + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "mobile": 0, + "model": "", + "bitness": "64", + "architecture": "x86" + } + }, + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "test": 0, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1671449004622, + "targeting": { + "includewinners": true, + "includebidderkeys": false + }, + "debug": true, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + } + } + }, + "tmax": 1000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://rtb.conative.network/openrtb2/auction", + "body": { + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "topframe": 1 + }, + "ext": { + "prebid": { + "bidder": { + "definemedia": {} + } + }, + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" + } + } + ], + "site": { + "domain": "localhost:8080", + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "publisher": { + "id": "1", + "domain": "localhost:8080" + } + }, + "device": { + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "sua": { + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "mobile": 0, + "architecture": "x86", + "bitness": "64", + "source": 2 + }, + "h": 1169, + "w": 1098, + "language": "de" + }, + "tmax": 1000, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1.671449004622e+12, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + }, + "debug": true, + "targeting": { + "includebidderkeys": false, + "includewinners": true + } + } + } + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/adapters/definemedia/definemediatest/supplemental/status_418.json b/adapters/definemedia/definemediatest/supplemental/status_418.json new file mode 100644 index 00000000000..6e82f90476e --- /dev/null +++ b/adapters/definemedia/definemediatest/supplemental/status_418.json @@ -0,0 +1,224 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", + "prebid": { + "bidder": { + "definemedia": {} + } + } + }, + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "topframe": 1, + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "domain": "localhost:8080", + "publisher": { + "domain": "localhost:8080", + "id": "1" + } + }, + "device": { + "w": 1098, + "h": 1169, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "language": "de", + "sua": { + "source": 2, + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "mobile": 0, + "model": "", + "bitness": "64", + "architecture": "x86" + } + }, + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "test": 0, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1671449004622, + "targeting": { + "includewinners": true, + "includebidderkeys": false + }, + "debug": true, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + } + } + }, + "tmax": 1000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://rtb.conative.network/openrtb2/auction", + "body": { + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "topframe": 1 + }, + "ext": { + "prebid": { + "bidder": { + "definemedia": {} + } + }, + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" + } + } + ], + "site": { + "domain": "localhost:8080", + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "publisher": { + "id": "1", + "domain": "localhost:8080" + } + }, + "device": { + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "sua": { + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "mobile": 0, + "architecture": "x86", + "bitness": "64", + "source": 2 + }, + "h": 1169, + "w": 1098, + "language": "de" + }, + "tmax": 1000, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1.671449004622e+12, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + }, + "debug": true, + "targeting": { + "includebidderkeys": false, + "includewinners": true + } + } + } + } + }, + "mockResponse": { + "status": 418, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 418. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/adapters/definemedia/definemediatest/supplemental/unmarshal-error.json b/adapters/definemedia/definemediatest/supplemental/unmarshal-error.json new file mode 100644 index 00000000000..396d6821c36 --- /dev/null +++ b/adapters/definemedia/definemediatest/supplemental/unmarshal-error.json @@ -0,0 +1,224 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", + "prebid": { + "bidder": { + "definemedia": {} + } + } + }, + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "topframe": 1, + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "domain": "localhost:8080", + "publisher": { + "domain": "localhost:8080", + "id": "1" + } + }, + "device": { + "w": 1098, + "h": 1169, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "language": "de", + "sua": { + "source": 2, + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "mobile": 0, + "model": "", + "bitness": "64", + "architecture": "x86" + } + }, + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "test": 0, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1671449004622, + "targeting": { + "includewinners": true, + "includebidderkeys": false + }, + "debug": true, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + } + } + }, + "tmax": 1000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://rtb.conative.network/openrtb2/auction", + "body": { + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "topframe": 1 + }, + "ext": { + "prebid": { + "bidder": { + "definemedia": {} + } + }, + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" + } + } + ], + "site": { + "domain": "localhost:8080", + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "publisher": { + "id": "1", + "domain": "localhost:8080" + } + }, + "device": { + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "sua": { + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "mobile": 0, + "architecture": "x86", + "bitness": "64", + "source": 2 + }, + "h": 1169, + "w": 1098, + "language": "de" + }, + "tmax": 1000, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1.671449004622e+12, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + }, + "debug": true, + "targeting": { + "includebidderkeys": false, + "includewinners": true + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "fail for unmarshall" + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/adapters/definemedia/definemediatest/supplemental/unsupported-type.json b/adapters/definemedia/definemediatest/supplemental/unsupported-type.json new file mode 100644 index 00000000000..91bfbc413c1 --- /dev/null +++ b/adapters/definemedia/definemediatest/supplemental/unsupported-type.json @@ -0,0 +1,250 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9", + "prebid": { + "bidder": { + "definemedia": {} + } + } + }, + "id": "div-gpt-ad-1460505748561-0", + "video": { + "w": 300, + "h": 250, + "maxduration": 60, + "minduration": 1, + "api": [1, 2, 5, 6, 7], + "mimes": ["video/mp4"], + "placement": 4, + "protocols": [2] + } + } + ], + "site": { + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "domain": "localhost:8080", + "publisher": { + "domain": "localhost:8080", + "id": "1" + } + }, + "device": { + "w": 1098, + "h": 1169, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "language": "de", + "sua": { + "source": 2, + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "mobile": 0, + "model": "", + "bitness": "64", + "architecture": "x86" + } + }, + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "test": 0, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1671449004622, + "targeting": { + "includewinners": true, + "includebidderkeys": false + }, + "debug": true, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + } + } + }, + "tmax": 1000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://rtb.conative.network/openrtb2/auction", + "body": { + "id": "ceb63773-4280-45f2-a684-94cf6a3b8fcf", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "video": { + "w": 300, + "h": 250, + "maxduration": 60, + "minduration": 1, + "api": [1, 2, 5, 6, 7], + "mimes": ["video/mp4"], + "placement": 4, + "protocols": [2] + }, + "ext": { + "prebid": { + "bidder": { + "definemedia": {} + } + }, + "tid": "397962d3-47a1-4634-9cb7-78597b01d9a9" + } + } + ], + "site": { + "domain": "localhost:8080", + "page": "http://localhost:8080/prebidServer_example.html?pbjs_debug=true", + "publisher": { + "id": "1", + "domain": "localhost:8080" + } + }, + "device": { + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "sua": { + "browsers": [ + { + "brand": "Not?A_Brand", + "version": [ + "8", + "0", + "0", + "0" + ] + }, + { + "brand": "Chromium", + "version": [ + "108", + "0", + "5359", + "124" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "108", + "0", + "5359", + "124" + ] + } + ], + "platform": { + "brand": "macOS", + "version": [ + "12", + "3", + "1" + ] + }, + "mobile": 0, + "architecture": "x86", + "bitness": "64", + "source": 2 + }, + "h": 1169, + "w": 1098, + "language": "de" + }, + "tmax": 1000, + "source": { + "tid": "ceb63773-4280-45f2-a684-94cf6a3b8fcf" + }, + "ext": { + "prebid": { + "auctiontimestamp": 1.671449004622e+12, + "channel": { + "name": "pbjs", + "version": "v7.28.0" + }, + "debug": true, + "targeting": { + "includebidderkeys": false, + "includewinners": true + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "46189656-7e2e-477d-b7f2-e05de224bb89", + "impid": "div-gpt-ad-1460505748561-0", + "price": 100, + "adm": "{banner html}", + "adomain": [ + "test.com" + ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "video" + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [{"currency":"USD","bids":[]}], + "expectedMakeBidsErrors": [ + { + "value": "Invalid mediatype in the impression", + "comparison": "literal" + } + ] +} diff --git a/adapters/definemedia/params_test.go b/adapters/definemedia/params_test.go new file mode 100644 index 00000000000..63ef5272669 --- /dev/null +++ b/adapters/definemedia/params_test.go @@ -0,0 +1,48 @@ +package definemedia + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderDefinemedia, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderDefinemedia, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"mandantId":123}`, + `{"mandantId":123, "adslotId":456}`, +} + +var invalidParams = []string{ + `{"mandantId": "42"}`, + `{"MandantId": "42"}`, + `{"mandantId":123, "adslotId":"456"}`, + `{"adslotId":456}`, + `{"adslotId":"456"}`, + `{}`, +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 6ab0dccfc7c..fd1a99aeffb 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -71,6 +71,7 @@ import ( "github.com/prebid/prebid-server/adapters/datablocks" "github.com/prebid/prebid-server/adapters/decenterads" "github.com/prebid/prebid-server/adapters/deepintent" + "github.com/prebid/prebid-server/adapters/definemedia" "github.com/prebid/prebid-server/adapters/dianomi" "github.com/prebid/prebid-server/adapters/dmx" "github.com/prebid/prebid-server/adapters/dxkulture" @@ -264,6 +265,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderDatablocks: datablocks.Builder, openrtb_ext.BidderDecenterAds: decenterads.Builder, openrtb_ext.BidderDeepintent: deepintent.Builder, + openrtb_ext.BidderDefinemedia: definemedia.Builder, openrtb_ext.BidderDianomi: dianomi.Builder, openrtb_ext.BidderEdge226: edge226.Builder, openrtb_ext.BidderDmx: dmx.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index a5a232bbd93..89d58d06800 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -117,7 +117,6 @@ func GetDisabledBidderWarningMessages(infos config.BidderInfos) map[string]strin "engagebdr": `Bidder "engagebdr" is no longer available in Prebid Server. Please update your configuration.`, "ninthdecimal": `Bidder "ninthdecimal" is no longer available in Prebid Server. Please update your configuration.`, "kubient": `Bidder "kubient" is no longer available in Prebid Server. Please update your configuration.`, - "definemedia": `Bidder "definemedia" is no longer available in Prebid Server. Please update your configuration.`, "applogy": `Bidder "applogy" is no longer available in Prebid Server. Please update your configuration.`, "rhythmone": `Bidder "rhythmone" is no longer available in Prebid Server. Please update your configuration.`, "nanointeractive": `Bidder "nanointeractive" is no longer available in Prebid Server. Please update your configuration.`, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 22a915b769f..fbd5bb9f787 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -87,6 +87,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderDatablocks, BidderDecenterAds, BidderDeepintent, + BidderDefinemedia, BidderDianomi, BidderEdge226, BidderDmx, @@ -368,6 +369,7 @@ const ( BidderDatablocks BidderName = "datablocks" BidderDecenterAds BidderName = "decenterads" BidderDeepintent BidderName = "deepintent" + BidderDefinemedia BidderName = "definemedia" BidderDianomi BidderName = "dianomi" BidderEdge226 BidderName = "edge226" BidderDmx BidderName = "dmx" diff --git a/openrtb_ext/imp_definemedia.go b/openrtb_ext/imp_definemedia.go new file mode 100644 index 00000000000..aa94bf5de63 --- /dev/null +++ b/openrtb_ext/imp_definemedia.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtDefinemedia struct { + MandantID int64 `json:"mandantId"` + AdslotID int64 `json:"adslotId"` +} diff --git a/static/bidder-info/definemedia.yaml b/static/bidder-info/definemedia.yaml new file mode 100644 index 00000000000..a7be6fb9d23 --- /dev/null +++ b/static/bidder-info/definemedia.yaml @@ -0,0 +1,10 @@ +endpoint: "https://rtb.conative.network/openrtb2/auction" +maintainer: + email: "development@definemedia.de" + gvlVendorID: 440 # GDPR vendor list ID +capabilities: + + site: + mediaTypes: + - banner + - native diff --git a/static/bidder-params/definemedia.json b/static/bidder-params/definemedia.json new file mode 100644 index 00000000000..bf5566b4c89 --- /dev/null +++ b/static/bidder-params/definemedia.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Define Media Adapter Params", + "description": "A schema which validates params accepted by the DM adapter", + "type": "object", + + "properties": { + "mandantId": { + "type": "integer", + "description": "The DEFINE-MEDIA mandant id. This is a unique identifier for your account. Please contact your account manager for more information." + }, + + "adslotId":{ + "type": "integer", + "description": "The adslot id. This is a unique identifier for your adslot and may change on subparts on a website. Please contact your account manager for more information." + } + }, + "required": ["mandantId"] + } \ No newline at end of file From 53e0adcaf0af3203fd4e235d1f4fe4a4cce80a79 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Fri, 20 Oct 2023 08:24:10 -0700 Subject: [PATCH 073/138] Adapter Name Case Insensitive: Stored Bid Responses (#3197) --- endpoints/openrtb2/amp_auction.go | 2 +- endpoints/openrtb2/auction.go | 37 +- endpoints/openrtb2/auction_test.go | 43 +- ...ed-bid-resp-case-matching-bidder-name.json | 39 ++ ...d-resp-case-not-matching-bidder-name.json} | 2 +- ...red-bid-resp-non-existing-bidder-name.json | 34 ++ .../imp-with-stored-bid-resp.json | 6 +- .../req-two-imps-stored-bid-responses.json | 10 +- ...with-and-without-stored-bid-responses.json | 4 +- exchange/utils.go | 2 +- exchange/utils_test.go | 23 + stored_responses/stored_responses.go | 115 ++-- stored_responses/stored_responses_test.go | 571 ++++++++++-------- 13 files changed, 529 insertions(+), 359 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-matching-bidder-name.json rename endpoints/openrtb2/sample-requests/valid-whole/supplementary/{imp-with-stored-bid-resp-insensitive-bidder-name.json => imp-with-stored-bid-resp-case-not-matching-bidder-name.json} (94%) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-non-existing-bidder-name.json diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 76b0e83cf3d..099f5d66c41 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -530,7 +530,7 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req return } - storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errs = stored_responses.ProcessStoredResponses(ctx, requestJSON, deps.storedRespFetcher, deps.bidderMap) + storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errs = stored_responses.ProcessStoredResponses(ctx, &openrtb_ext.RequestWrapper{BidRequest: req}, deps.storedRespFetcher) if err != nil { errs = []error{err} return diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 2cad7a2463a..e6c9fe1fbde 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -517,12 +517,6 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric return } - //Stored auction responses should be processed after stored requests due to possible impression modification - storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errs = stored_responses.ProcessStoredResponses(ctx, requestJson, deps.storedRespFetcher, deps.bidderMap) - if len(errs) > 0 { - return nil, nil, nil, nil, nil, nil, errs - } - if err := jsonutil.UnmarshalValid(requestJson, req.BidRequest); err != nil { errs = []error{err} return @@ -548,6 +542,12 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric lmt.ModifyForIOS(req.BidRequest) + //Stored auction responses should be processed after stored requests due to possible impression modification + storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errs = stored_responses.ProcessStoredResponses(ctx, req, deps.storedRespFetcher) + if len(errs) > 0 { + return nil, nil, nil, nil, nil, nil, errs + } + hasStoredResponses := len(storedAuctionResponses) > 0 errL := deps.validateRequest(req, false, hasStoredResponses, storedBidResponses, hasStoredBidRequest) if len(errL) > 0 { @@ -1532,10 +1532,8 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb_ext.ImpWrapper, aliases ma return []error{fmt.Errorf("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[%d].ext.prebid \n", impIndex)} } - if len(storedBidResp) > 0 { - if err := validateStoredBidRespAndImpExtBidders(prebid.Bidder, storedBidResp, imp.ID); err != nil { - return []error{err} - } + if err := deps.validateStoredBidRespAndImpExtBidders(prebid, storedBidResp, imp.ID); err != nil { + return []error{err} } errL := []error{} @@ -2502,19 +2500,24 @@ func checkIfAppRequest(request []byte) (bool, error) { return false, nil } -func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage, storedBidResp stored_responses.ImpBidderStoredResp, impId string) error { +func (deps *endpointDeps) validateStoredBidRespAndImpExtBidders(prebid *openrtb_ext.ExtImpPrebid, storedBidResp stored_responses.ImpBidderStoredResp, impId string) error { + if storedBidResp == nil && len(prebid.StoredBidResponse) == 0 { + return nil + } + + if storedBidResp == nil { + return generateStoredBidResponseValidationError(impId) + } if bidResponses, ok := storedBidResp[impId]; ok { - if len(bidResponses) != len(bidderExts) { + if len(bidResponses) != len(prebid.Bidder) { return generateStoredBidResponseValidationError(impId) } for bidderName := range bidResponses { - bidder := bidderName - normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidder) - if ok { - bidder = normalizedCoreBidder.String() + if _, bidderNameOk := deps.normalizeBidderName(bidderName); !bidderNameOk { + return fmt.Errorf(`unrecognized bidder "%v"`, bidderName) } - if _, present := bidderExts[bidder]; !present { + if _, present := prebid.Bidder[bidderName]; !present { return generateStoredBidResponseValidationError(impId) } } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 530d197e2ae..bab25261175 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -4991,8 +4991,8 @@ func TestParseRequestStoredResponses(t *testing.T) { } func TestParseRequestStoredBidResponses(t *testing.T) { - bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "testBidder1"}], "bidid": "123", "cur": "USD"}`) - bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2"}], "seat": "testBidder2"}], "bidid": "124", "cur": "USD"}`) + bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "telaria"}], "bidid": "123", "cur": "USD"}`) + bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2"}], "seat": "amx"}], "bidid": "124", "cur": "USD"}`) bidRespId3 := json.RawMessage(`{"id": "resp_id3", "seatbid": [{"bid": [{"id": "bid_id3"}], "seat": "APPNEXUS"}], "bidid": "125", "cur": "USD"}`) mockStoredBidResponses := map[string]json.RawMessage{ "bidResponseId1": bidRespId1, @@ -5011,15 +5011,23 @@ func TestParseRequestStoredBidResponses(t *testing.T) { name: "req imp has valid stored bid response", givenRequestBody: validRequest(t, "imp-with-stored-bid-resp.json"), expectedStoredBidResponses: map[string]map[string]json.RawMessage{ - "imp-id1": {"testBidder1": bidRespId1}, + "imp-id1": {"telaria": bidRespId1}, }, expectedErrorCount: 0, }, { - name: "req imp has valid stored bid response with case insensitive bidder name", - givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-insensitive-bidder-name.json"), + name: "req imp has valid stored bid response with case not-matching bidder name", + givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-case-not-matching-bidder-name.json"), expectedStoredBidResponses: map[string]map[string]json.RawMessage{ - "imp-id3": {"APPNEXUS": bidRespId3}, + "imp-id3": {"appnexus": bidRespId3}, + }, + expectedErrorCount: 0, + }, + { + name: "req imp has valid stored bid response with case matching bidder name", + givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-case-matching-bidder-name.json"), + expectedStoredBidResponses: map[string]map[string]json.RawMessage{ + "imp-id3": {"appnexus": bidRespId3}, }, expectedErrorCount: 0, }, @@ -5027,8 +5035,8 @@ func TestParseRequestStoredBidResponses(t *testing.T) { name: "req has two imps with valid stored bid responses", givenRequestBody: validRequest(t, "req-two-imps-stored-bid-responses.json"), expectedStoredBidResponses: map[string]map[string]json.RawMessage{ - "imp-id1": {"testBidder1": bidRespId1}, - "imp-id2": {"testBidder2": bidRespId2}, + "imp-id1": {"telaria": bidRespId1}, + "imp-id2": {"amx": bidRespId2}, }, expectedErrorCount: 0, }, @@ -5036,7 +5044,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) { name: "req has two imps one with valid stored bid responses and another one without stored bid responses", givenRequestBody: validRequest(t, "req-two-imps-with-and-without-stored-bid-responses.json"), expectedStoredBidResponses: map[string]map[string]json.RawMessage{ - "imp-id2": {"testBidder2": bidRespId2}, + "imp-id2": {"amx": bidRespId2}, }, expectedErrorCount: 0, }, @@ -5044,7 +5052,13 @@ func TestParseRequestStoredBidResponses(t *testing.T) { name: "req has two imps with missing stored bid responses", givenRequestBody: validRequest(t, "req-two-imps-missing-stored-bid-response.json"), expectedStoredBidResponses: nil, - expectedErrorCount: 2, + expectedErrorCount: 1, + }, + { + name: "req imp has valid stored bid response with non existing bidder name", + givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-non-existing-bidder-name.json"), + expectedStoredBidResponses: nil, + expectedErrorCount: 1, }, } for _, test := range tests { @@ -5063,7 +5077,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) { map[string]string{}, false, []byte{}, - map[string]openrtb_ext.BidderName{"testBidder1": "testBidder1", "testBidder2": "testBidder2", "appnexus": "appnexus"}, + map[string]openrtb_ext.BidderName{"telaria": "telaria", "amx": "amx", "appnexus": "appnexus"}, nil, nil, hardcodedResponseIPValidator{response: true}, @@ -5078,6 +5092,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) { req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody)) _, _, _, storedBidResponses, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor) if test.expectedErrorCount == 0 { + assert.Empty(t, errL) assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses, "stored responses should match") } else { assert.Contains(t, errL[0].Error(), test.expectedError, "error should match") @@ -5653,8 +5668,10 @@ func TestValidateStoredResp(t *testing.T) { } for _, test := range testCases { - errorList := deps.validateRequest(test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses, false) - assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + t.Run(test.description, func(t *testing.T) { + errorList := deps.validateRequest(test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses, false) + assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + }) } } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-matching-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-matching-bidder-name.json new file mode 100644 index 00000000000..49576023b04 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-matching-bidder-name.json @@ -0,0 +1,39 @@ +{ + "description": "request with impression with stored bid response with case matching bidder name", + "mockBidRequest": { + "id": "request-with-stored-resp", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id3", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "prebid": { + "storedbidresponse": [ + { + "bidder": "appnexus", + "id": "bidResponseId3" + } + ] + } + } + } + ], + "user": { + "yob": 1989 + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-insensitive-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-not-matching-bidder-name.json similarity index 94% rename from endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-insensitive-bidder-name.json rename to endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-not-matching-bidder-name.json index e7b688d4d83..bcebe81472d 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-insensitive-bidder-name.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-not-matching-bidder-name.json @@ -1,5 +1,5 @@ { - "description": "request with impression with stored bid response with sensitive bidder name", + "description": "request with impression with stored bid response with case not matching bidder name", "mockBidRequest": { "id": "request-with-stored-resp", "site": { diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-non-existing-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-non-existing-bidder-name.json new file mode 100644 index 00000000000..d8685307e20 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-non-existing-bidder-name.json @@ -0,0 +1,34 @@ +{ + "description": "request with impression with stored bid response", + "mockBidRequest": { + "id": "request-with-stored-resp", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id1", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidderABC": {}, + "prebid": { + "storedbidresponse": [ + {"bidder":"bidderABC", "id": "bidResponseId1"} + ] + } + } + } + ], + "user": { + "yob": 1989 + } + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json index e72cce49355..692a34c4f41 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json @@ -17,12 +17,10 @@ ] }, "ext": { - "appnexus": { - "placementId": 12883451 - }, + "telaria": {}, "prebid": { "storedbidresponse": [ - {"bidder":"testBidder1", "id": "bidResponseId1"} + {"bidder":"telaria", "id": "bidResponseId1"} ] } } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json index 5906eb9149c..09b4bc57746 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json @@ -17,12 +17,12 @@ ] }, "ext": { - "appnexus": { + "telaria": { "placementId": 12883451 }, "prebid": { "storedbidresponse": [ - {"bidder":"testBidder1", "id": "bidResponseId1"} + {"bidder":"telaria", "id": "bidResponseId1"} ] } } @@ -38,12 +38,10 @@ ] }, "ext": { - "appnexus": { - "placementId": 12883451 - }, + "amx": {}, "prebid": { "storedbidresponse": [ - {"bidder":"testBidder2", "id": "bidResponseId2"} + {"bidder":"amx", "id": "bidResponseId2"} ] } } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json index 6122e4df066..5387a9d61ae 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json @@ -33,12 +33,12 @@ ] }, "ext": { - "appnexus": { + "amx": { "placementId": 12883451 }, "prebid": { "storedbidresponse": [ - {"bidder":"testBidder2", "id": "bidResponseId2"} + {"bidder":"amx", "id": "bidResponseId2"} ] } } diff --git a/exchange/utils.go b/exchange/utils.go index c7acfaefce3..ea6b317dae1 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -958,7 +958,7 @@ func buildBidResponseRequest(req *openrtb2.BidRequest, BidderCoreName: resolvedBidder, BidderName: bidderName, BidderStoredResponses: impResps, - ImpReplaceImpId: bidderImpReplaceImpID[string(resolvedBidder)], + ImpReplaceImpId: bidderImpReplaceImpID[string(bidderName)], IsRequestAlias: isRequestAlias, BidderLabels: metrics.AdapterLabels{Adapter: resolvedBidder}, } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index b413a6b292e..3c28a43c33e 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/stored_responses" "sort" "testing" @@ -4754,3 +4755,25 @@ func TestApplyBidAdjustmentToFloor(t *testing.T) { }) } } + +func TestBuildBidResponseRequestBidderName(t *testing.T) { + bidderImpResponses := stored_responses.BidderImpsWithBidResponses{ + openrtb_ext.BidderName("appnexus"): {"impId1": json.RawMessage(`{}`), "impId2": json.RawMessage(`{}`)}, + openrtb_ext.BidderName("appneXUS"): {"impId3": json.RawMessage(`{}`), "impId4": json.RawMessage(`{}`)}, + } + + bidderImpReplaceImpID := stored_responses.BidderImpReplaceImpID{ + "appnexus": {"impId1": true, "impId2": false}, + "appneXUS": {"impId3": true, "impId4": false}, + } + result := buildBidResponseRequest(nil, bidderImpResponses, nil, bidderImpReplaceImpID) + + resultAppnexus := result["appnexus"] + assert.Equal(t, resultAppnexus.BidderName, openrtb_ext.BidderName("appnexus")) + assert.Equal(t, resultAppnexus.ImpReplaceImpId, map[string]bool{"impId1": true, "impId2": false}) + + resultAppneXUS := result["appneXUS"] + assert.Equal(t, resultAppneXUS.BidderName, openrtb_ext.BidderName("appneXUS")) + assert.Equal(t, resultAppneXUS.ImpReplaceImpId, map[string]bool{"impId3": true, "impId4": false}) + +} diff --git a/stored_responses/stored_responses.go b/stored_responses/stored_responses.go index ef8fe50619d..d6944a6f30f 100644 --- a/stored_responses/stored_responses.go +++ b/stored_responses/stored_responses.go @@ -4,12 +4,11 @@ import ( "context" "encoding/json" "fmt" + "strings" - "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/jsonutil" ) type ImpsWithAuctionResponseIDs map[string]string @@ -57,8 +56,7 @@ func buildStoredResp(storedBidResponses ImpBidderStoredResp) BidderImpsWithBidRe return bidderToImpToResponses } -func extractStoredResponsesIds(impInfo []ImpExtPrebidData, - bidderMap map[string]openrtb_ext.BidderName) ( +func extractStoredResponsesIds(impInfo []*openrtb_ext.ImpWrapper) ( StoredResponseIDs, ImpBiddersWithBidResponseIDs, ImpsWithAuctionResponseIDs, @@ -76,53 +74,62 @@ func extractStoredResponsesIds(impInfo []ImpExtPrebidData, impBidderReplaceImp := ImpBidderReplaceImpID{} for index, impData := range impInfo { - impId, err := jsonparser.GetString(impData.Imp, "id") + impId := impData.ID + impExt, err := impData.GetImpExt() if err != nil { - return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] missing required field: \"id\"", index) + return nil, nil, nil, nil, err + } + impExtPrebid := impExt.GetPrebid() + if impExtPrebid == nil { + continue } - if impData.ImpExtPrebid.StoredAuctionResponse != nil { - if len(impData.ImpExtPrebid.StoredAuctionResponse.ID) == 0 { + if impExtPrebid.StoredAuctionResponse != nil { + if len(impExtPrebid.StoredAuctionResponse.ID) == 0 { return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ", index) } - allStoredResponseIDs = append(allStoredResponseIDs, impData.ImpExtPrebid.StoredAuctionResponse.ID) + allStoredResponseIDs = append(allStoredResponseIDs, impExtPrebid.StoredAuctionResponse.ID) - impAuctionResponseIDs[impId] = impData.ImpExtPrebid.StoredAuctionResponse.ID + impAuctionResponseIDs[impId] = impExtPrebid.StoredAuctionResponse.ID } - if len(impData.ImpExtPrebid.StoredBidResponse) > 0 { + if len(impExtPrebid.StoredBidResponse) > 0 { + + // bidders can be specified in imp.ext and in imp.ext.prebid.bidders + allBidderNames := make([]string, 0) + for bidderName := range impExtPrebid.Bidder { + allBidderNames = append(allBidderNames, bidderName) + } + for extData := range impExt.GetExt() { + // no bidders will not be processed + allBidderNames = append(allBidderNames, extData) + } bidderStoredRespId := make(map[string]string) bidderReplaceImpId := make(map[string]bool) - for _, bidderResp := range impData.ImpExtPrebid.StoredBidResponse { + for _, bidderResp := range impExtPrebid.StoredBidResponse { if len(bidderResp.ID) == 0 || len(bidderResp.Bidder) == 0 { return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ", index) } - bidderName := bidderResp.Bidder - normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidderResp.Bidder) - if ok { - bidderName = normalizedCoreBidder.String() - } - //check if bidder is valid/exists - if _, isValid := bidderMap[bidderName]; !isValid { - return nil, nil, nil, nil, fmt.Errorf("request.imp[impId: %s].ext.prebid.bidder contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impId, bidderResp.Bidder) - } - // bidder is unique per one bid stored response - // if more than one bidder specified the last defined bidder id will take precedence - bidderStoredRespId[bidderResp.Bidder] = bidderResp.ID - impBiddersWithBidResponseIDs[impId] = bidderStoredRespId - - // stored response config can specify if imp id should be replaced with imp id from request - replaceImpId := true - if bidderResp.ReplaceImpId != nil { - // replaceimpid is true if not specified - replaceImpId = *bidderResp.ReplaceImpId - } - bidderReplaceImpId[bidderResp.Bidder] = replaceImpId - impBidderReplaceImp[impId] = bidderReplaceImpId - //storedAuctionResponseIds are not unique, but fetch will return single data for repeated ids - allStoredResponseIDs = append(allStoredResponseIDs, bidderResp.ID) + for _, bidderName := range allBidderNames { + if _, found := bidderStoredRespId[bidderName]; !found && strings.EqualFold(bidderName, bidderResp.Bidder) { + bidderStoredRespId[bidderName] = bidderResp.ID + impBiddersWithBidResponseIDs[impId] = bidderStoredRespId + + // stored response config can specify if imp id should be replaced with imp id from request + replaceImpId := true + if bidderResp.ReplaceImpId != nil { + // replaceimpid is true if not specified + replaceImpId = *bidderResp.ReplaceImpId + } + bidderReplaceImpId[bidderName] = replaceImpId + impBidderReplaceImp[impId] = bidderReplaceImpId + + //storedAuctionResponseIds are not unique, but fetch will return single data for repeated ids + allStoredResponseIDs = append(allStoredResponseIDs, bidderResp.ID) + } + } } } } @@ -135,14 +142,11 @@ func extractStoredResponsesIds(impInfo []ImpExtPrebidData, // Note that processStoredResponses must be called after processStoredRequests // because stored imps and stored requests can contain stored auction responses and stored bid responses // so the stored requests/imps have to be merged into the incoming request prior to processing stored auction responses. -func ProcessStoredResponses(ctx context.Context, requestJson []byte, storedRespFetcher stored_requests.Fetcher, bidderMap map[string]openrtb_ext.BidderName) (ImpsWithBidResponses, ImpBidderStoredResp, BidderImpReplaceImpID, []error) { - impInfo, errs := parseImpInfo(requestJson) - if len(errs) > 0 { - return nil, nil, nil, errs - } - storedResponsesIds, impBidderToStoredBidResponseId, impIdToRespId, impBidderReplaceImp, err := extractStoredResponsesIds(impInfo, bidderMap) +func ProcessStoredResponses(ctx context.Context, requestWrapper *openrtb_ext.RequestWrapper, storedRespFetcher stored_requests.Fetcher) (ImpsWithBidResponses, ImpBidderStoredResp, BidderImpReplaceImpID, []error) { + + storedResponsesIds, impBidderToStoredBidResponseId, impIdToRespId, impBidderReplaceImp, err := extractStoredResponsesIds(requestWrapper.GetImp()) if err != nil { - return nil, nil, nil, append(errs, err) + return nil, nil, nil, []error{err} } if len(storedResponsesIds) > 0 { @@ -201,28 +205,3 @@ func buildStoredResponsesMaps(storedResponses StoredResponseIdToStoredResponse, } return impIdToStoredResp, impBidderToStoredBidResponse, errs } - -// parseImpInfo parses the request JSON and returns the impressions with their unmarshalled imp.ext.prebid -// copied from exchange to isolate stored responses code from auction dependencies -func parseImpInfo(requestJson []byte) (impData []ImpExtPrebidData, errs []error) { - - if impArray, dataType, _, err := jsonparser.Get(requestJson, "imp"); err == nil && dataType == jsonparser.Array { - _, err = jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, err error) { - impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid") - var impExtPrebid openrtb_ext.ExtImpPrebid - if impExtData != nil { - if err := jsonutil.Unmarshal(impExtData, &impExtPrebid); err != nil { - errs = append(errs, err) - } - } - newImpData := ImpExtPrebidData{imp, impExtPrebid} - impData = append(impData, newImpData) - }) - } - return -} - -type ImpExtPrebidData struct { - Imp json.RawMessage - ImpExtPrebid openrtb_ext.ExtImpPrebid -} diff --git a/stored_responses/stored_responses_test.go b/stored_responses/stored_responses_test.go index c4ddea278a7..209b34a650b 100644 --- a/stored_responses/stored_responses_test.go +++ b/stored_responses/stored_responses_test.go @@ -163,136 +163,109 @@ func TestBuildStoredBidResponses(t *testing.T) { } func TestProcessStoredAuctionAndBidResponsesErrors(t *testing.T) { - bidderMap := map[string]openrtb_ext.BidderName{"testBidder": "testBidder"} - testCases := []struct { description string - requestJson []byte + request openrtb2.BidRequest expectedErrorList []error }{ { description: "Invalid stored auction response format: empty stored Auction Response Id", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { - "storedauctionresponse": { - } - } - } - } - ]}`), + "storedauctionresponse": {} + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ")}, }, { description: "Invalid stored bid response format: empty storedbidresponse.bidder", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { "storedbidresponse": [ { "id": "123abc"}] - } - } - } - ]}`), + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")}, }, { description: "Invalid stored bid response format: empty storedbidresponse.id", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { "storedbidresponse": [ { "bidder": "testbidder"}] - } - } - } - ]}`), + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")}, }, - { - description: "Invalid stored bid response: storedbidresponse.bidder not found", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "prebid": { - "storedbidresponse": [ - { "bidder": "testBidder123", "id": "123abc"}] - } - } - } - ]}`), - expectedErrorList: []error{errors.New("request.imp[impId: imp-id1].ext.prebid.bidder contains unknown bidder: testBidder123. Did you forget an alias in request.ext.prebid.aliases?")}, - }, { description: "Invalid stored auction response format: empty stored Auction Response Id in second imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { "storedauctionresponse": { "id":"123" } - } - } - }, - { - "id": "imp-id2", - "ext": { + }}`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "prebid": { - "storedauctionresponse": { + "storedauctionresponse": { "id":"" } - } - } - } - ]}`), + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[1] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ")}, }, { description: "Invalid stored bid response format: empty stored bid Response Id in second imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { - "storedbidresponse": [ + "storedbidresponse": [ {"bidder":"testBidder", "id": "123abc"} ] - } - } - }, - { - "id": "imp-id2", - "ext": { + }}`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "prebid": { - "storedbidresponse": [ + "storedbidresponse": [ {"bidder":"testBidder", "id": ""} ] - } - } - } - ]}`), + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[1] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")}, }, } for _, test := range testCases { - _, _, _, errorList := ProcessStoredResponses(nil, test.requestJson, nil, bidderMap) - assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + t.Run(test.description, func(t *testing.T) { + rw := &openrtb_ext.RequestWrapper{BidRequest: &test.request} + _, _, _, errorList := ProcessStoredResponses(nil, rw, nil) + assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + }) } } func TestProcessStoredAuctionAndBidResponses(t *testing.T) { - bidderMap := map[string]openrtb_ext.BidderName{"bidderA": "bidderA", "bidderB": "bidderB"} bidStoredResp1 := json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "bidderA"}]`) bidStoredResp2 := json.RawMessage(`[{"bid": [{"id": "bid_id2"],"seat": "bidderB"}]`) bidStoredResp3 := json.RawMessage(`[{"bid": [{"id": "bid_id3"],"seat": "bidderA"}]`) @@ -305,33 +278,31 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { testCases := []struct { description string - requestJson []byte + request openrtb2.BidRequest expectedStoredAuctionResponses ImpsWithBidResponses expectedStoredBidResponses ImpBidderStoredResp expectedBidderImpReplaceImpID BidderImpReplaceImpID }{ { description: "No stored responses", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "prebid": { - - } - } - } - ]}`), + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "prebid": {} + }`)}, + }, + }, expectedStoredAuctionResponses: nil, expectedStoredBidResponses: nil, expectedBidderImpReplaceImpID: nil, }, { description: "Stored auction response one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -340,9 +311,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "1" } } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, }, @@ -351,11 +322,11 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored bid response one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { "placementId": 123 }, "prebid": { @@ -363,9 +334,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderA", "id": "1"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{}, expectedStoredBidResponses: ImpBidderStoredResp{ "imp-id1": {"bidderA": bidStoredResp1}, @@ -376,11 +347,14 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored bid responses two bidders one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "bidderB": { "placementId": 123 }, "prebid": { @@ -389,9 +363,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderB", "id": "2", "replaceimpid": false} ] } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{}, expectedStoredBidResponses: ImpBidderStoredResp{ "imp-id1": {"bidderA": bidStoredResp1, "bidderB": bidStoredResp2}, @@ -401,14 +375,120 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "bidderB": map[string]bool{"imp-id1": false}, }, }, + { + description: "Stored bid responses two same mixed case bidders one imp", + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "BIDDERa": { + "placementId": 123 + }, + "prebid": { + "storedbidresponse": [ + {"bidder":"bidderA", "id": "1", "replaceimpid": true}, + {"bidder":"bidderB", "id": "2", "replaceimpid": false} + ] + } + }`)}, + }, + }, + expectedStoredAuctionResponses: ImpsWithBidResponses{}, + expectedStoredBidResponses: ImpBidderStoredResp{ + "imp-id1": {"bidderA": bidStoredResp1, "BIDDERa": bidStoredResp1}, + }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "BIDDERa": map[string]bool{"imp-id1": true}, + "bidderA": map[string]bool{"imp-id1": true}, + }, + }, + { + description: "Stored bid responses 3 same mixed case bidders in imp.ext and imp.ext.prebid.bidders one imp", + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "BIDDERa": { + "placementId": 123 + }, + "prebid": { + "bidder": { + "BiddeRa": { + "placementId": 12883451 + } + }, + "storedbidresponse": [ + {"bidder":"bidderA", "id": "1", "replaceimpid": true}, + {"bidder":"bidderB", "id": "2", "replaceimpid": false} + ] + } + }`)}, + }, + }, + expectedStoredAuctionResponses: ImpsWithBidResponses{}, + expectedStoredBidResponses: ImpBidderStoredResp{ + "imp-id1": {"bidderA": bidStoredResp1, "BIDDERa": bidStoredResp1, "BiddeRa": bidStoredResp1}, + }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "BIDDERa": map[string]bool{"imp-id1": true}, + "bidderA": map[string]bool{"imp-id1": true}, + "BiddeRa": map[string]bool{"imp-id1": true}, + }, + }, + { + description: "Stored bid responses 3 same mixed case bidders in imp.ext and imp.ext.prebid.bidders one imp, duplicated stored response", + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "BIDDERa": { + "placementId": 123 + }, + "prebid": { + "bidder": { + "BiddeRa": { + "placementId": 12883451 + } + }, + "storedbidresponse": [ + {"bidder":"bidderA", "id": "1", "replaceimpid": true}, + {"bidder":"bidderA", "id": "2", "replaceimpid": true}, + {"bidder":"bidderB", "id": "2", "replaceimpid": false} + ] + } + }`)}, + }, + }, + expectedStoredAuctionResponses: ImpsWithBidResponses{}, + expectedStoredBidResponses: ImpBidderStoredResp{ + "imp-id1": {"bidderA": bidStoredResp1, "BIDDERa": bidStoredResp1, "BiddeRa": bidStoredResp1}, + }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "BIDDERa": map[string]bool{"imp-id1": true}, + "bidderA": map[string]bool{"imp-id1": true}, + "BiddeRa": map[string]bool{"imp-id1": true}, + }, + }, { //This is not a valid scenario for real auction request, added for testing purposes description: "Stored auction and bid responses one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "bidderB": { "placementId": 123 }, "prebid": { @@ -420,9 +500,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderB", "id": "2"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, }, @@ -436,10 +516,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored auction response three imps", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -448,11 +528,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "1" } } - } - }, - { - "id": "imp-id2", - "ext": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -461,11 +539,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "2" } } - } - }, + }`)}, { - "id": "imp-id3", - "ext": { + ID: "imp-id3", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -474,9 +551,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "3" } } - } - } - ]}`), + }`), + }, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, "imp-id2": bidStoredResp2, @@ -487,10 +565,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored auction response three imps duplicated stored auction response", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -499,11 +577,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "1" } } - } - }, - { - "id": "imp-id2", - "ext": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -512,11 +588,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "2" } } - } - }, + }`)}, { - "id": "imp-id3", - "ext": { + ID: "imp-id3", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -525,9 +600,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "2" } } - } - } - ]}`), + }`), + }, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, "imp-id2": bidStoredResp2, @@ -538,11 +614,14 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored bid responses two bidders two imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "bidderB": { "placementId": 123 }, "prebid": { @@ -551,12 +630,13 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderB", "id": "2"} ] } - } - }, - { - "id": "imp-id2", - "ext": { - "appnexus": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "bidderB": { "placementId": 123 }, "prebid": { @@ -565,9 +645,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderB", "id": "2", "replaceimpid": false} ] } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{}, expectedStoredBidResponses: ImpBidderStoredResp{ "imp-id1": {"bidderA": bidStoredResp1, "bidderB": bidStoredResp2}, @@ -581,17 +661,19 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { } for _, test := range testCases { - storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errorList := ProcessStoredResponses(nil, test.requestJson, fetcher, bidderMap) - assert.Equal(t, test.expectedStoredAuctionResponses, storedAuctionResponses, "storedAuctionResponses doesn't match: %s\n", test.description) - assert.Equalf(t, test.expectedStoredBidResponses, storedBidResponses, "storedBidResponses doesn't match: %s\n", test.description) - assert.Equal(t, test.expectedBidderImpReplaceImpID, bidderImpReplaceImpId, "bidderImpReplaceImpId doesn't match: %s\n", test.description) - assert.Nil(t, errorList, "Error should be nil") + t.Run(test.description, func(t *testing.T) { + rw := openrtb_ext.RequestWrapper{BidRequest: &test.request} + storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errorList := ProcessStoredResponses(nil, &rw, fetcher) + assert.Equal(t, test.expectedStoredAuctionResponses, storedAuctionResponses) + assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses) + assert.Equal(t, test.expectedBidderImpReplaceImpID, bidderImpReplaceImpId) + assert.Nil(t, errorList, "Error should be nil") + }) } } func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { - bidderMap := map[string]openrtb_ext.BidderName{"bidderA": "bidderA", "bidderB": "bidderB"} bidStoredResp1 := json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "bidderA"}]`) bidStoredResp2 := json.RawMessage(`[{"bid": [{"id": "bid_id2"],"seat": "bidderB"}]`) mockStoredResponses := map[string]json.RawMessage{ @@ -604,16 +686,16 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { testCases := []struct { description string - requestJson []byte + request openrtb2.BidRequest expectedErrors []error }{ { description: "Stored bid response with nil data, one bidder one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -621,20 +703,20 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "3"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored bid response for impId = imp-id1, bidder = bidderB and storedBidResponse id = 3"), }, }, { description: "Stored bid response with nil data, one bidder, two imps, one with correct stored response", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -642,12 +724,10 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "1"} ] } - } - }, - { - "id": "imp-id2", - "ext": { - "appnexus": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -655,20 +735,20 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "3"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored bid response for impId = imp-id2, bidder = bidderB and storedBidResponse id = 3"), }, }, { description: "Stored bid response with nil data, one bidder, two imps, both with correct stored response", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -676,12 +756,10 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "4"} ] } - } - }, - { - "id": "imp-id2", - "ext": { - "appnexus": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -689,9 +767,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "3"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored bid response for impId = imp-id1, bidder = bidderB and storedBidResponse id = 4"), errors.New("failed to fetch stored bid response for impId = imp-id2, bidder = bidderB and storedBidResponse id = 3"), @@ -699,10 +777,10 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { }, { description: "Stored auction response with nil data and one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -711,19 +789,19 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "4" } } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored auction response for impId = imp-id1 and storedAuctionResponse id = 4"), }, }, { description: "Stored auction response with nil data, and two imps with nil responses", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -732,11 +810,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "4" } } - } - }, - { - "id": "imp-id2", - "ext": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -745,9 +821,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "3" } } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored auction response for impId = imp-id1 and storedAuctionResponse id = 4"), errors.New("failed to fetch stored auction response for impId = imp-id2 and storedAuctionResponse id = 3"), @@ -755,10 +831,10 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { }, { description: "Stored auction response with nil data, two imps, one with nil responses", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -767,11 +843,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "2" } } - } - }, - { - "id": "imp-id2", - "ext": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -780,9 +854,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "3" } } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored auction response for impId = imp-id2 and storedAuctionResponse id = 3"), }, @@ -790,10 +864,13 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { } for _, test := range testCases { - _, _, _, errorList := ProcessStoredResponses(nil, test.requestJson, fetcher, bidderMap) - for _, err := range test.expectedErrors { - assert.Contains(t, errorList, err, "incorrect errors returned: %s", test.description) - } + t.Run(test.description, func(t *testing.T) { + rw := openrtb_ext.RequestWrapper{BidRequest: &test.request} + _, _, _, errorList := ProcessStoredResponses(nil, &rw, fetcher) + for _, err := range test.expectedErrors { + assert.Contains(t, errorList, err) + } + }) } } @@ -847,8 +924,10 @@ func TestFlipMap(t *testing.T) { } for _, test := range testCases { - actualResult := flipMap(test.inImpBidderReplaceImpID) - assert.Equal(t, test.outBidderImpReplaceImpID, actualResult, "Incorrect flipped map for test case %s\n", test.description) + t.Run(test.description, func(t *testing.T) { + actualResult := flipMap(test.inImpBidderReplaceImpID) + assert.Equal(t, test.outBidderImpReplaceImpID, actualResult) + }) } } From 6b98a812c2d8adf69e9ff45eaeb108f8097231f3 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Fri, 20 Oct 2023 14:26:41 -0400 Subject: [PATCH 074/138] Adapter Name Case Insensitive: alternate bidder codes (#3229) --- .../exchangetest/alternate-bidder-codes.json | 272 ++++++++++++++++++ exchange/utils.go | 30 +- exchange/utils_test.go | 221 +++++++++++++- openrtb_ext/alternatebiddercodes.go | 31 +- openrtb_ext/alternatebiddercodes_test.go | 148 ++++++++++ 5 files changed, 683 insertions(+), 19 deletions(-) create mode 100644 exchange/exchangetest/alternate-bidder-codes.json diff --git a/exchange/exchangetest/alternate-bidder-codes.json b/exchange/exchangetest/alternate-bidder-codes.json new file mode 100644 index 00000000000..26a7a05d4e1 --- /dev/null +++ b/exchange/exchangetest/alternate-bidder-codes.json @@ -0,0 +1,272 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pubmatic": { + "publisherId": 5890 + }, + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "ext": { + "prebid": { + "alternatebiddercodes": { + "enabled": true, + "bidders": { + "PUBmatic": { + "enabled": true, + "allowedbiddercodes": [ + "groupm" + ] + } + } + } + } + } + } + }, + "outgoingRequests": { + "pubmatic": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": 5890 + } + } + } + ], + "ext": { + "prebid": { + "alternatebiddercodes": { + "enabled": true, + "bidders": { + "pubmatic": { + "enabled": true, + "allowedbiddercodes": [ + "groupm" + ] + } + } + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "pubmatic-bid-1", + "impid": "imp-id-1", + "price": 0.71 + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + } + ], + "seat": "pubmatic" + }, + { + "pbsBids": [ + { + "ortbBid": { + "id": "pubmatic-bid-2", + "impid": "imp-id-1", + "price": 0.51 + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + } + ], + "seat": "groupm" + } + ] + } + }, + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "alternatebiddercodes": { + "enabled": true, + "bidders": null + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "appnexus-bid-1", + "impid": "imp-id-1", + "price": 0.3 + }, + "bidType": "banner", + "bidMeta": { + "adaptercode": "appnexus" + } + } + ], + "seat": "appnexus" + }, + { + "pbsBids": [ + { + "ortbBid": { + "id": "appnexus-bid-2", + "impid": "imp-id-1", + "price": 0.3 + }, + "bidType": "banner", + "bidMeta": { + "adaptercode": "appnexus" + } + } + ], + "seat": "groupm" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "groupm", + "bid": [ + { + "id": "pubmatic-bid-2", + "impid": "imp-id-1", + "price": 0.51, + "ext": { + "origbidcpm": 0.51, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video" + } + } + }, + { + "id": "appnexus-bid-2", + "impid": "imp-id-1", + "price": 0.3, + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ] + }, + { + "seat": "pubmatic", + "bid": [ + { + "id": "pubmatic-bid-1", + "impid": "imp-id-1", + "price": 0.71, + "ext": { + "origbidcpm": 0.71, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video" + } + } + } + ] + }, + { + "seat": "appnexus", + "bid": [ + { + "id": "appnexus-bid-1", + "impid": "imp-id-1", + "price": 0.3, + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/utils.go b/exchange/utils.go index ea6b317dae1..8b4774a36f4 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -442,30 +442,32 @@ func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, request } func buildRequestExtAlternateBidderCodes(bidder string, accABC *openrtb_ext.ExtAlternateBidderCodes, reqABC *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes { - if reqABC != nil { - alternateBidderCodes := &openrtb_ext.ExtAlternateBidderCodes{ - Enabled: reqABC.Enabled, - } - if bidderCodes, ok := reqABC.Bidders[bidder]; ok { - alternateBidderCodes.Bidders = map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ - bidder: bidderCodes, - } - } - return alternateBidderCodes + + if altBidderCodes := copyExtAlternateBidderCodes(bidder, reqABC); altBidderCodes != nil { + return altBidderCodes } - if accABC != nil { + if altBidderCodes := copyExtAlternateBidderCodes(bidder, accABC); altBidderCodes != nil { + return altBidderCodes + } + + return nil +} + +func copyExtAlternateBidderCodes(bidder string, altBidderCodes *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes { + if altBidderCodes != nil { alternateBidderCodes := &openrtb_ext.ExtAlternateBidderCodes{ - Enabled: accABC.Enabled, + Enabled: altBidderCodes.Enabled, } - if bidderCodes, ok := accABC.Bidders[bidder]; ok { + + if bidderCodes, ok := altBidderCodes.IsBidderInAlternateBidderCodes(bidder); ok { alternateBidderCodes.Bidders = map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ bidder: bidderCodes, } } + return alternateBidderCodes } - return nil } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 3c28a43c33e..3a00c37c7b1 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -5,10 +5,11 @@ import ( "encoding/json" "errors" "fmt" - "github.com/prebid/prebid-server/stored_responses" "sort" "testing" + "github.com/prebid/prebid-server/stored_responses" + gpplib "github.com/prebid/go-gpp" "github.com/prebid/go-gpp/constants" "github.com/prebid/openrtb/v19/openrtb2" @@ -4756,6 +4757,224 @@ func TestApplyBidAdjustmentToFloor(t *testing.T) { } } +func TestBuildRequestExtAlternateBidderCodes(t *testing.T) { + type testInput struct { + bidderNameRaw string + accABC *openrtb_ext.ExtAlternateBidderCodes + reqABC *openrtb_ext.ExtAlternateBidderCodes + } + testCases := []struct { + desc string + in testInput + expected *openrtb_ext.ExtAlternateBidderCodes + }{ + { + desc: "No biddername, nil reqABC and accABC", + in: testInput{}, + expected: nil, + }, + { + desc: "No biddername, non-nil reqABC", + in: testInput{ + reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "No biddername, non-nil accABC", + in: testInput{ + accABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "No biddername, non-nil reqABC nor accABC", + in: testInput{ + reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, + accABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "non-nil reqABC", + in: testInput{ + bidderNameRaw: "pubmatic", + reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "non-nil accABC", + in: testInput{ + bidderNameRaw: "pubmatic", + accABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "both reqABC and accABC enabled and bidder matches elements in accABC but reqABC comes first", + in: testInput{ + bidderNameRaw: "PUBmatic", + reqABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"pubCode1"}, + }, + }, + }, + accABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "PubMatic": { + AllowedBidderCodes: []string{"pubCode2"}, + }, + }, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true}, + }, + { + desc: "both reqABC and accABC enabled and bidder matches elements in both but we prioritize reqABC", + in: testInput{ + bidderNameRaw: "pubmatic", + reqABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "PubMatic": { + AllowedBidderCodes: []string{"pubCode"}, + }, + }, + }, + accABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"anxsCode"}, + }, + }, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": { + AllowedBidderCodes: []string{"pubCode"}, + }, + }, + }, + }, + { + desc: "nil reqABC non-nil accABC enabled and bidder matches elements in accABC", + in: testInput{ + bidderNameRaw: "APPnexus", + accABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"anxsCode"}, + }, + }, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "APPnexus": { + AllowedBidderCodes: []string{"anxsCode"}, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + alternateBidderCodes := buildRequestExtAlternateBidderCodes(tc.in.bidderNameRaw, tc.in.accABC, tc.in.reqABC) + assert.Equal(t, tc.expected, alternateBidderCodes) + }) + } +} + +func TestCopyExtAlternateBidderCodes(t *testing.T) { + type testInput struct { + bidder string + alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes + } + testCases := []struct { + desc string + in testInput + expected *openrtb_ext.ExtAlternateBidderCodes + }{ + { + desc: "pass a nil alternateBidderCodes argument, expect nil output", + in: testInput{}, + expected: nil, + }, + { + desc: "non-nil alternateBidderCodes argument but bidder doesn't match", + in: testInput{ + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + }, + }, + { + desc: "non-nil alternateBidderCodes argument bidder is identical to one element in map", + in: testInput{ + bidder: "appnexus", + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"adnxs"}, + }, + }, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"adnxs"}, + }, + }, + }, + }, + { + desc: "case insensitive match, keep bidder casing in output", + in: testInput{ + bidder: "AppNexus", + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"adnxs"}, + }, + }, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "AppNexus": { + AllowedBidderCodes: []string{"adnxs"}, + }, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + alternateBidderCodes := copyExtAlternateBidderCodes(tc.in.bidder, tc.in.alternateBidderCodes) + assert.Equal(t, tc.expected, alternateBidderCodes) + }) + } +} + func TestBuildBidResponseRequestBidderName(t *testing.T) { bidderImpResponses := stored_responses.BidderImpsWithBidResponses{ openrtb_ext.BidderName("appnexus"): {"impId1": json.RawMessage(`{}`), "impId2": json.RawMessage(`{}`)}, diff --git a/openrtb_ext/alternatebiddercodes.go b/openrtb_ext/alternatebiddercodes.go index 49291a3e5e8..3dcb6c781fa 100644 --- a/openrtb_ext/alternatebiddercodes.go +++ b/openrtb_ext/alternatebiddercodes.go @@ -1,6 +1,9 @@ package openrtb_ext -import "fmt" +import ( + "fmt" + "strings" +) // ExtAlternateBidderCodes defines list of alternate bidder codes allowed by adatpers. This overrides host level configs. type ExtAlternateBidderCodes struct { @@ -14,7 +17,7 @@ type ExtAdapterAlternateBidderCodes struct { } func (bidderCodes *ExtAlternateBidderCodes) IsValidBidderCode(bidder, alternateBidder string) (bool, error) { - if alternateBidder == "" || bidder == alternateBidder { + if alternateBidder == "" || strings.EqualFold(bidder, alternateBidder) { return true, nil } @@ -26,8 +29,8 @@ func (bidderCodes *ExtAlternateBidderCodes) IsValidBidderCode(bidder, alternateB return false, alternateBidderNotDefinedError(bidder, alternateBidder) } - adapterCfg, ok := bidderCodes.Bidders[bidder] - if !ok { + adapterCfg, found := bidderCodes.IsBidderInAlternateBidderCodes(bidder) + if !found { return false, alternateBidderNotDefinedError(bidder, alternateBidder) } @@ -56,3 +59,23 @@ func alternateBidderDisabledError(bidder, alternateBidder string) error { func alternateBidderNotDefinedError(bidder, alternateBidder string) error { return fmt.Errorf("alternateBidderCodes not defined for adapter %q, rejecting bids for %q", bidder, alternateBidder) } + +// IsBidderInAlternateBidderCodes tries to find bidder in the altBidderCodes.Bidders map in a case sensitive +// manner first. If no match is found it'll try it in a case insensitive way in linear time +func (bidderCodes *ExtAlternateBidderCodes) IsBidderInAlternateBidderCodes(bidder string) (ExtAdapterAlternateBidderCodes, bool) { + if len(bidder) > 0 && bidderCodes != nil && len(bidderCodes.Bidders) > 0 { + // try constant time exact match + if adapterCfg, found := bidderCodes.Bidders[bidder]; found { + return adapterCfg, true + } + + // check if we can find with a case insensitive comparison + for bidderName, adapterCfg := range bidderCodes.Bidders { + if strings.EqualFold(bidder, bidderName) { + return adapterCfg, true + } + } + } + + return ExtAdapterAlternateBidderCodes{}, false +} diff --git a/openrtb_ext/alternatebiddercodes_test.go b/openrtb_ext/alternatebiddercodes_test.go index 438aebad559..f9229b13d2a 100644 --- a/openrtb_ext/alternatebiddercodes_test.go +++ b/openrtb_ext/alternatebiddercodes_test.go @@ -35,6 +35,14 @@ func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) { }, wantIsValid: true, }, + { + name: "alternateBidder and bidder are the same under Unicode case-folding (default non-extra bid case with seat's alternateBidder explicitly set)", + args: args{ + bidder: "pubmatic", + alternateBidder: "pubmatic", + }, + wantIsValid: true, + }, { name: "account.alternatebiddercodes config not defined (default, reject bid)", args: args{ @@ -98,6 +106,20 @@ func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) { }, wantIsValid: true, }, + { + name: "bidder is different in casing than the entry in account.alternatebiddercodes but they match because our case insensitive comparison", + args: args{ + bidder: "PUBmatic", + alternateBidder: "groupm", + }, + fields: fields{ + Enabled: true, + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "pubmatic": {Enabled: true}, + }, + }, + wantIsValid: true, + }, { name: "allowedBidderCodes is *", args: args{ @@ -178,3 +200,129 @@ func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) { }) } } + +func TestIsBidderInAlternateBidderCodes(t *testing.T) { + type testInput struct { + bidder string + bidderCodes *ExtAlternateBidderCodes + } + type testOutput struct { + adapterCfg ExtAdapterAlternateBidderCodes + found bool + } + testCases := []struct { + desc string + in testInput + expected testOutput + }{ + { + desc: "empty bidder", + in: testInput{ + bidderCodes: &ExtAlternateBidderCodes{}, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + { + desc: "nil ExtAlternateBidderCodes", + in: testInput{ + bidder: "appnexus", + bidderCodes: nil, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + { + desc: "nil ExtAlternateBidderCodes.Bidder map", + in: testInput{ + bidder: "appnexus", + bidderCodes: &ExtAlternateBidderCodes{}, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + { + desc: "nil ExtAlternateBidderCodes.Bidder map", + in: testInput{ + bidder: "appnexus", + bidderCodes: &ExtAlternateBidderCodes{ + Bidders: nil, + }, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + { + desc: "bidder arg identical to entry in Bidders map", + in: testInput{ + bidder: "appnexus", + bidderCodes: &ExtAlternateBidderCodes{ + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "appnexus": { + Enabled: true, + AllowedBidderCodes: []string{"abcCode"}, + }, + }, + }, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{ + Enabled: true, + AllowedBidderCodes: []string{"abcCode"}, + }, + found: true, + }, + }, + { + desc: "bidder arg matches an entry in Bidders map with case insensitive comparisson", + in: testInput{ + bidder: "appnexus", + bidderCodes: &ExtAlternateBidderCodes{ + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "AppNexus": {AllowedBidderCodes: []string{"adnxsCode"}}, + "PubMatic": {AllowedBidderCodes: []string{"pubCode"}}, + "Rubicon": {AllowedBidderCodes: []string{"rCode"}}, + }, + }, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{ + AllowedBidderCodes: []string{"adnxsCode"}, + }, + found: true, + }, + }, + { + desc: "bidder arg doesn't match any entry in map", + in: testInput{ + bidder: "unknown", + bidderCodes: &ExtAlternateBidderCodes{ + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "AppNexus": {AllowedBidderCodes: []string{"adnxsCode"}}, + "PubMatic": {AllowedBidderCodes: []string{"pubCode"}}, + "Rubicon": {AllowedBidderCodes: []string{"rCode"}}, + }, + }, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + adapterCfg, found := tc.in.bidderCodes.IsBidderInAlternateBidderCodes(tc.in.bidder) + assert.Equal(t, tc.expected.adapterCfg, adapterCfg) + assert.Equal(t, tc.expected.found, found) + }) + } +} From ec729e6e5d50a1306d76332fda52108b9d313c1d Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Fri, 20 Oct 2023 16:22:32 -0400 Subject: [PATCH 075/138] Increment Package Version To V2 (#3245) --- .gitignore | 4 +- account/account.go | 14 +- account/account_test.go | 12 +- adapters/33across/33across.go | 8 +- adapters/33across/33across_test.go | 6 +- adapters/33across/params_test.go | 2 +- adapters/aax/aax.go | 8 +- adapters/aax/aax_test.go | 6 +- adapters/aax/params_test.go | 2 +- adapters/aceex/aceex.go | 10 +- adapters/aceex/aceex_test.go | 6 +- adapters/aceex/params_test.go | 2 +- adapters/acuityads/acuityads.go | 10 +- adapters/acuityads/acuityads_test.go | 6 +- adapters/acuityads/params_test.go | 2 +- adapters/adapterstest/test_json.go | 6 +- adapters/adf/adf.go | 8 +- adapters/adf/adf_test.go | 6 +- adapters/adf/params_test.go | 2 +- adapters/adgeneration/adgeneration.go | 8 +- adapters/adgeneration/adgeneration_test.go | 8 +- adapters/adgeneration/params_test.go | 2 +- adapters/adhese/adhese.go | 10 +- adapters/adhese/adhese_test.go | 6 +- adapters/adhese/params_test.go | 2 +- adapters/adkernel/adkernel.go | 10 +- adapters/adkernel/adkernel_test.go | 6 +- adapters/adkernelAdn/adkernelAdn.go | 10 +- adapters/adkernelAdn/adkernelAdn_test.go | 6 +- adapters/adman/adman.go | 8 +- adapters/adman/adman_test.go | 6 +- adapters/adman/params_test.go | 2 +- adapters/admixer/admixer.go | 8 +- adapters/admixer/admixer_test.go | 6 +- adapters/admixer/params_test.go | 2 +- adapters/adnuntius/adnuntius.go | 10 +- adapters/adnuntius/adnuntius_test.go | 8 +- adapters/adnuntius/params_test.go | 2 +- adapters/adocean/adocean.go | 10 +- adapters/adocean/adocean_test.go | 6 +- adapters/adocean/params_test.go | 2 +- adapters/adoppler/adoppler.go | 10 +- adapters/adoppler/adoppler_test.go | 6 +- adapters/adot/adot.go | 8 +- adapters/adot/adot_test.go | 8 +- adapters/adot/params_test.go | 2 +- adapters/adpone/adpone.go | 8 +- adapters/adpone/adpone_test.go | 6 +- adapters/adprime/adprime.go | 8 +- adapters/adprime/adprime_test.go | 6 +- adapters/adprime/params_test.go | 2 +- adapters/adquery/adquery.go | 8 +- adapters/adquery/adquery_test.go | 7 +- adapters/adquery/params_test.go | 2 +- adapters/adquery/types.go | 2 +- adapters/adrino/adrino.go | 8 +- adapters/adrino/adrino_test.go | 6 +- adapters/adrino/params_test.go | 2 +- adapters/adsinteractive/adsinteractive.go | 8 +- .../adsinteractive/adsinteractive_test.go | 6 +- adapters/adsinteractive/params_test.go | 2 +- adapters/adtarget/adtarget.go | 8 +- adapters/adtarget/adtarget_test.go | 6 +- adapters/adtarget/params_test.go | 2 +- adapters/adtelligent/adtelligent.go | 8 +- adapters/adtelligent/adtelligent_test.go | 6 +- adapters/adtelligent/params_test.go | 2 +- adapters/adtrgtme/adtrgtme.go | 8 +- adapters/adtrgtme/adtrgtme_test.go | 6 +- adapters/adtrgtme/params_test.go | 2 +- adapters/advangelists/advangelists.go | 10 +- adapters/advangelists/advangelists_test.go | 6 +- adapters/advangelists/params_test.go | 3 +- adapters/adview/adview.go | 10 +- adapters/adview/adview_test.go | 6 +- adapters/adview/params_test.go | 2 +- adapters/adxcg/adxcg.go | 8 +- adapters/adxcg/adxcg_test.go | 6 +- adapters/adyoulike/adyoulike.go | 8 +- adapters/adyoulike/adyoulike_test.go | 6 +- adapters/adyoulike/params_test.go | 2 +- adapters/aidem/aidem.go | 8 +- adapters/aidem/aidem_test.go | 6 +- adapters/aidem/params_test.go | 2 +- adapters/aja/aja.go | 8 +- adapters/aja/aja_test.go | 6 +- adapters/algorix/algorix.go | 10 +- adapters/algorix/algorix_test.go | 6 +- adapters/algorix/params_test.go | 3 +- adapters/amx/amx.go | 8 +- adapters/amx/amx_test.go | 8 +- adapters/amx/params_test.go | 2 +- adapters/apacdex/apacdex.go | 8 +- adapters/apacdex/apacdex_test.go | 6 +- adapters/apacdex/params_test.go | 2 +- adapters/appnexus/appnexus.go | 18 +- adapters/appnexus/appnexus_test.go | 6 +- adapters/appnexus/params_test.go | 2 +- adapters/appush/appush.go | 8 +- adapters/appush/appush_test.go | 6 +- adapters/appush/params_test.go | 2 +- adapters/audienceNetwork/facebook.go | 12 +- adapters/audienceNetwork/facebook_test.go | 8 +- adapters/automatad/automatad.go | 8 +- adapters/automatad/automatad_test.go | 6 +- adapters/automatad/params_test.go | 2 +- adapters/avocet/avocet.go | 8 +- adapters/avocet/avocet_test.go | 10 +- adapters/axis/axis.go | 8 +- adapters/axis/axis_test.go | 6 +- adapters/axis/params_test.go | 2 +- adapters/axonix/axonix.go | 10 +- adapters/axonix/axonix_test.go | 6 +- adapters/axonix/params_test.go | 2 +- adapters/beachfront/beachfront.go | 8 +- adapters/beachfront/beachfront_test.go | 6 +- adapters/beachfront/params_test.go | 2 +- adapters/beintoo/beintoo.go | 8 +- adapters/beintoo/beintoo_test.go | 6 +- adapters/beintoo/params_test.go | 2 +- adapters/bematterfull/bematterfull.go | 10 +- .../bematterfull/bematterfulltest_test.go | 6 +- adapters/bematterfull/params_test.go | 2 +- adapters/between/between.go | 10 +- adapters/between/between_test.go | 6 +- adapters/between/params_test.go | 2 +- adapters/beyondmedia/beyondmedia.go | 8 +- adapters/beyondmedia/beyondmedia_test.go | 6 +- adapters/beyondmedia/params_test.go | 2 +- adapters/bidder.go | 8 +- adapters/bidmachine/bidmachine.go | 10 +- adapters/bidmachine/bidmachine_test.go | 6 +- adapters/bidmachine/params_test.go | 2 +- adapters/bidmyadz/bidmyadz.go | 8 +- adapters/bidmyadz/bidmyadz_test.go | 6 +- adapters/bidmyadz/params_test.go | 2 +- adapters/bidscube/bidscube.go | 8 +- adapters/bidscube/bidscube_test.go | 6 +- adapters/bidscube/params_test.go | 2 +- adapters/bidstack/bidstack.go | 6 +- adapters/bidstack/bidstack_test.go | 6 +- adapters/bidstack/params_test.go | 2 +- adapters/bizzclick/bizzclick.go | 10 +- adapters/bizzclick/bizzclick_test.go | 6 +- adapters/bizzclick/params_test.go | 2 +- adapters/bliink/bliink.go | 8 +- adapters/bliink/bliink_test.go | 6 +- adapters/bliink/params_test.go | 3 +- adapters/blue/blue.go | 8 +- adapters/blue/blue_test.go | 6 +- adapters/bluesea/bluesea.go | 8 +- adapters/bluesea/bluesea_test.go | 6 +- adapters/bluesea/params_test.go | 2 +- adapters/bmtm/brightmountainmedia.go | 8 +- adapters/bmtm/brightmountainmedia_test.go | 6 +- adapters/bmtm/params_test.go | 2 +- adapters/boldwin/boldwin.go | 8 +- adapters/boldwin/boldwin_test.go | 6 +- adapters/boldwin/params_test.go | 2 +- adapters/brave/brave.go | 10 +- adapters/brave/brave_test.go | 6 +- adapters/brave/params_test.go | 2 +- .../cadent_aperture_mx/cadentaperturemx.go | 8 +- .../cadentaperturemx_test.go | 8 +- adapters/cadent_aperture_mx/params_test.go | 2 +- adapters/ccx/ccx.go | 8 +- adapters/ccx/ccx_test.go | 6 +- adapters/ccx/params_test.go | 2 +- adapters/coinzilla/coinzilla.go | 8 +- adapters/coinzilla/coinzilla_test.go | 6 +- adapters/coinzilla/params_test.go | 2 +- adapters/colossus/colossus.go | 8 +- adapters/colossus/colossus_test.go | 6 +- adapters/colossus/params_test.go | 2 +- adapters/compass/compass.go | 8 +- adapters/compass/compass_test.go | 6 +- adapters/compass/params_test.go | 2 +- adapters/connectad/connectad.go | 8 +- adapters/connectad/connectad_test.go | 6 +- adapters/connectad/params_test.go | 2 +- adapters/consumable/consumable.go | 10 +- adapters/consumable/consumable_test.go | 8 +- adapters/consumable/params_test.go | 2 +- adapters/conversant/conversant.go | 8 +- adapters/conversant/conversant_test.go | 6 +- adapters/cpmstar/cpmstar.go | 8 +- adapters/cpmstar/cpmstar_test.go | 6 +- adapters/cpmstar/params_test.go | 2 +- adapters/criteo/criteo.go | 8 +- adapters/criteo/criteo_test.go | 7 +- adapters/criteo/params_test.go | 2 +- adapters/cwire/cwire.go | 8 +- adapters/cwire/cwire_test.go | 6 +- adapters/cwire/params_test.go | 2 +- adapters/datablocks/datablocks.go | 10 +- adapters/datablocks/datablocks_test.go | 6 +- adapters/decenterads/decenterads.go | 8 +- adapters/decenterads/decenterads_test.go | 6 +- adapters/decenterads/params_test.go | 2 +- adapters/deepintent/deepintent.go | 8 +- adapters/deepintent/deepintent_test.go | 6 +- adapters/deepintent/params_test.go | 2 +- adapters/definemedia/definemedia.go | 8 +- adapters/definemedia/definemedia_test.go | 6 +- adapters/definemedia/params_test.go | 2 +- adapters/dianomi/dianomi.go | 8 +- adapters/dianomi/dianomi_test.go | 6 +- adapters/dianomi/params_test.go | 2 +- adapters/dmx/dmx.go | 8 +- adapters/dmx/dmx_test.go | 8 +- adapters/dmx/params_test.go | 3 +- adapters/dxkulture/dxkulture.go | 8 +- adapters/dxkulture/dxkulture_test.go | 4 +- adapters/dxkulture/params_test.go | 2 +- adapters/e_volution/evolution.go | 8 +- adapters/e_volution/evolution_test.go | 6 +- adapters/e_volution/params_test.go | 2 +- adapters/edge226/edge226.go | 6 +- adapters/edge226/edge226_test.go | 6 +- adapters/edge226/params_test.go | 2 +- adapters/emtv/emtv.go | 8 +- adapters/emtv/emtv_test.go | 6 +- adapters/emtv/params_test.go | 2 +- adapters/eplanning/eplanning.go | 8 +- adapters/eplanning/eplanning_test.go | 8 +- adapters/epom/epom.go | 8 +- adapters/epom/epom_test.go | 6 +- adapters/flipp/flipp.go | 6 +- adapters/flipp/flipp_params.go | 2 +- adapters/flipp/flipp_test.go | 6 +- adapters/flipp/params_test.go | 2 +- adapters/freewheelssp/freewheelssp.go | 8 +- adapters/freewheelssp/freewheelssp_test.go | 7 +- adapters/frvradn/frvradn.go | 8 +- adapters/frvradn/frvradn_test.go | 9 +- adapters/frvradn/params_test.go | 2 +- adapters/gamma/gamma.go | 8 +- adapters/gamma/gamma_test.go | 6 +- adapters/gamma/params_test.go | 2 +- adapters/gamoshi/gamoshi.go | 8 +- adapters/gamoshi/gamoshi_test.go | 6 +- adapters/gamoshi/params_test.go | 2 +- adapters/globalsun/globalsun.go | 8 +- adapters/globalsun/globalsun_test.go | 6 +- adapters/globalsun/params_test.go | 2 +- adapters/gothamads/gothamads.go | 10 +- adapters/gothamads/gothamads_test.go | 6 +- adapters/gothamads/params_test.go | 2 +- adapters/grid/grid.go | 10 +- adapters/grid/grid_test.go | 6 +- adapters/gumgum/gumgum.go | 8 +- adapters/gumgum/gumgum_test.go | 6 +- adapters/gumgum/params_test.go | 3 +- adapters/huaweiads/huaweiads.go | 8 +- adapters/huaweiads/huaweiads_test.go | 6 +- adapters/huaweiads/params_test.go | 3 +- adapters/imds/imds.go | 10 +- adapters/imds/imds_test.go | 6 +- adapters/imds/params_test.go | 2 +- adapters/impactify/impactify.go | 8 +- adapters/impactify/impactify_test.go | 6 +- adapters/impactify/params_test.go | 2 +- adapters/improvedigital/improvedigital.go | 11 +- .../improvedigital/improvedigital_test.go | 6 +- adapters/improvedigital/params_test.go | 3 +- adapters/infoawarebidder.go | 6 +- adapters/infoawarebidder_test.go | 8 +- adapters/infytv/infytv.go | 8 +- adapters/infytv/infytv_test.go | 6 +- adapters/infytv/params_test.go | 2 +- adapters/inmobi/inmobi.go | 8 +- adapters/inmobi/inmobi_test.go | 6 +- .../interactiveoffers/interactiveoffers.go | 10 +- .../interactiveoffers_test.go | 6 +- adapters/interactiveoffers/params_test.go | 3 +- adapters/invibes/invibes.go | 12 +- adapters/invibes/invibes_test.go | 6 +- adapters/invibes/params_test.go | 2 +- adapters/iqx/iqx.go | 10 +- adapters/iqx/iqxtest_test.go | 6 +- adapters/iqx/params_test.go | 2 +- adapters/iqzone/iqzone.go | 8 +- adapters/iqzone/iqzone_test.go | 6 +- adapters/iqzone/params_test.go | 2 +- adapters/ix/ix.go | 10 +- adapters/ix/ix_test.go | 10 +- adapters/ix/params_test.go | 2 +- adapters/jixie/jixie.go | 8 +- adapters/jixie/jixie_test.go | 6 +- adapters/jixie/params_test.go | 2 +- adapters/kargo/kargo.go | 8 +- adapters/kargo/kargo_test.go | 6 +- adapters/kargo/params_test.go | 2 +- adapters/kayzen/kayzen.go | 10 +- adapters/kayzen/kayzen_test.go | 6 +- adapters/kayzen/params_test.go | 2 +- adapters/kidoz/kidoz.go | 8 +- adapters/kidoz/kidoz_test.go | 8 +- adapters/kidoz/params_test.go | 2 +- adapters/kiviads/kiviads.go | 8 +- adapters/kiviads/kiviads_test.go | 6 +- adapters/kiviads/params_test.go | 2 +- adapters/krushmedia/krushmedia.go | 10 +- adapters/krushmedia/krushmedia_test.go | 6 +- adapters/krushmedia/params_test.go | 2 +- adapters/lemmadigital/lemmadigital.go | 10 +- adapters/lemmadigital/lemmadigital_test.go | 6 +- adapters/lemmadigital/params_test.go | 2 +- adapters/liftoff/liftoff.go | 6 +- adapters/liftoff/liftoff_test.go | 6 +- adapters/liftoff/param_test.go | 2 +- adapters/limelightDigital/limelightDigital.go | 10 +- .../limelightDigital/limelightDigital_test.go | 9 +- adapters/limelightDigital/params_test.go | 2 +- adapters/lm_kiviads/lmkiviads.go | 10 +- adapters/lm_kiviads/lmkiviads_test.go | 6 +- adapters/lm_kiviads/params_test.go | 2 +- adapters/lockerdome/lockerdome.go | 8 +- adapters/lockerdome/lockerdome_test.go | 6 +- adapters/lockerdome/params_test.go | 2 +- adapters/logan/logan.go | 8 +- adapters/logan/logan_test.go | 6 +- adapters/logan/params_test.go | 2 +- adapters/logicad/logicad.go | 8 +- adapters/logicad/logicad_test.go | 6 +- adapters/logicad/params_test.go | 3 +- adapters/lunamedia/lunamedia.go | 10 +- adapters/lunamedia/lunamedia_test.go | 6 +- adapters/lunamedia/params_test.go | 3 +- adapters/mabidder/mabidder.go | 6 +- adapters/mabidder/mabidder_test.go | 6 +- adapters/mabidder/params_test.go | 2 +- adapters/madvertise/madvertise.go | 10 +- adapters/madvertise/madvertise_test.go | 6 +- adapters/madvertise/params_test.go | 2 +- adapters/marsmedia/marsmedia.go | 8 +- adapters/marsmedia/marsmedia_test.go | 6 +- adapters/marsmedia/params_test.go | 2 +- adapters/medianet/medianet.go | 8 +- adapters/medianet/medianet_test.go | 6 +- adapters/medianet/params_test.go | 2 +- adapters/mgid/mgid.go | 8 +- adapters/mgid/mgid_test.go | 6 +- adapters/mgidX/mgidX.go | 6 +- adapters/mgidX/mgidX_test.go | 6 +- adapters/mgidX/params_test.go | 2 +- adapters/mobfoxpb/mobfoxpb.go | 8 +- adapters/mobfoxpb/mobfoxpb_test.go | 6 +- adapters/mobfoxpb/params_test.go | 2 +- adapters/mobilefuse/mobilefuse.go | 10 +- adapters/mobilefuse/mobilefuse_test.go | 6 +- adapters/mobilefuse/params_test.go | 2 +- adapters/motorik/motorik.go | 10 +- adapters/motorik/motorik_test.go | 6 +- adapters/motorik/params_test.go | 2 +- adapters/nextmillennium/nextmillennium.go | 8 +- .../nextmillennium/nextmillennium_test.go | 6 +- adapters/nextmillennium/params_test.go | 2 +- adapters/nobid/nobid.go | 8 +- adapters/nobid/nobid_test.go | 6 +- adapters/nobid/params_test.go | 2 +- adapters/onetag/onetag.go | 10 +- adapters/onetag/onetag_test.go | 6 +- adapters/onetag/params_test.go | 2 +- adapters/openweb/openweb.go | 8 +- adapters/openweb/openweb_test.go | 6 +- adapters/openweb/params_test.go | 2 +- adapters/openx/openx.go | 8 +- adapters/openx/openx_test.go | 8 +- adapters/openx/params_test.go | 2 +- adapters/operaads/operaads.go | 10 +- adapters/operaads/operaads_test.go | 6 +- adapters/operaads/params_test.go | 2 +- adapters/orbidder/orbidder.go | 8 +- adapters/orbidder/orbidder_test.go | 8 +- adapters/orbidder/params_test.go | 2 +- adapters/outbrain/outbrain.go | 8 +- adapters/outbrain/outbrain_test.go | 6 +- adapters/outbrain/params_test.go | 2 +- adapters/ownadx/ownadx.go | 13 +- adapters/ownadx/ownadx_test.go | 9 +- adapters/pangle/pangle.go | 8 +- adapters/pangle/pangle_test.go | 6 +- adapters/pangle/param_test.go | 2 +- adapters/pgamssp/params_test.go | 2 +- adapters/pgamssp/pgamssp.go | 6 +- adapters/pgamssp/pgamssp_test.go | 6 +- adapters/pubmatic/params_test.go | 2 +- adapters/pubmatic/pubmatic.go | 8 +- adapters/pubmatic/pubmatic_test.go | 8 +- adapters/pubnative/pubnative.go | 8 +- adapters/pubnative/pubnative_test.go | 6 +- adapters/pulsepoint/params_test.go | 2 +- adapters/pulsepoint/pulsepoint.go | 8 +- adapters/pulsepoint/pulsepoint_test.go | 6 +- adapters/pwbid/params_test.go | 2 +- adapters/pwbid/pwbid.go | 8 +- adapters/pwbid/pwbid_test.go | 6 +- adapters/response.go | 2 +- adapters/response_test.go | 2 +- adapters/revcontent/revcontent.go | 8 +- adapters/revcontent/revcontent_test.go | 6 +- adapters/richaudience/params_test.go | 2 +- adapters/richaudience/richaudience.go | 8 +- adapters/richaudience/richaudience_test.go | 8 +- adapters/rise/rise.go | 6 +- adapters/rise/rise_test.go | 6 +- adapters/rtbhouse/rtbhouse.go | 8 +- adapters/rtbhouse/rtbhouse_test.go | 6 +- adapters/rubicon/rubicon.go | 10 +- adapters/rubicon/rubicon_test.go | 10 +- adapters/sa_lunamedia/params_test.go | 2 +- adapters/sa_lunamedia/salunamedia.go | 8 +- adapters/sa_lunamedia/salunamedia_test.go | 6 +- adapters/screencore/params_test.go | 2 +- adapters/screencore/screencore.go | 10 +- adapters/screencore/screencore_test.go | 6 +- adapters/seedingAlliance/params_test.go | 2 +- adapters/seedingAlliance/seedingAlliance.go | 8 +- .../seedingAlliance/seedingAlliance_test.go | 8 +- adapters/sharethrough/params_test.go | 2 +- adapters/sharethrough/sharethrough.go | 10 +- adapters/sharethrough/sharethrough_test.go | 6 +- adapters/silvermob/params_test.go | 2 +- adapters/silvermob/silvermob.go | 10 +- adapters/silvermob/silvermob_test.go | 6 +- adapters/silverpush/params_test.go | 2 +- adapters/silverpush/silverpush.go | 8 +- adapters/silverpush/silverpush_test.go | 8 +- adapters/smaato/image.go | 3 +- adapters/smaato/native.go | 3 +- adapters/smaato/params_test.go | 2 +- adapters/smaato/richmedia.go | 3 +- adapters/smaato/smaato.go | 12 +- adapters/smaato/smaato_test.go | 8 +- adapters/smartadserver/params_test.go | 2 +- adapters/smartadserver/smartadserver.go | 8 +- adapters/smartadserver/smartadserver_test.go | 6 +- adapters/smarthub/params_test.go | 2 +- adapters/smarthub/smarthub.go | 10 +- adapters/smarthub/smarthub_test.go | 6 +- adapters/smartrtb/smartrtb.go | 10 +- adapters/smartrtb/smartrtb_test.go | 6 +- adapters/smartx/params_test.go | 2 +- adapters/smartx/smartx.go | 6 +- adapters/smartx/smartx_test.go | 6 +- adapters/smartyads/params_test.go | 2 +- adapters/smartyads/smartyads.go | 10 +- adapters/smartyads/smartyads_test.go | 6 +- adapters/smilewanted/params_test.go | 2 +- adapters/smilewanted/smilewanted.go | 8 +- adapters/smilewanted/smilewanted_test.go | 6 +- adapters/sonobi/params_test.go | 2 +- adapters/sonobi/sonobi.go | 8 +- adapters/sonobi/sonobi_test.go | 6 +- adapters/sovrn/sovrn.go | 8 +- adapters/sovrn/sovrn_test.go | 6 +- adapters/sspBC/sspbc.go | 8 +- adapters/sspBC/sspbc_test.go | 6 +- adapters/stroeerCore/params_test.go | 3 +- adapters/stroeerCore/stroeercore.go | 8 +- adapters/stroeerCore/stroeercore_test.go | 6 +- adapters/suntContent/params_test.go | 2 +- adapters/suntContent/suntContent.go | 8 +- adapters/suntContent/suntContent_test.go | 8 +- adapters/taboola/params_test.go | 3 +- adapters/taboola/taboola.go | 10 +- adapters/taboola/taboola_test.go | 10 +- adapters/tappx/params_test.go | 3 +- adapters/tappx/tappx.go | 10 +- adapters/tappx/tappx_test.go | 6 +- adapters/teads/teads.go | 10 +- adapters/teads/teads_test.go | 9 +- adapters/telaria/params_test.go | 3 +- adapters/telaria/telaria.go | 8 +- adapters/telaria/telaria_test.go | 6 +- adapters/tpmn/params_test.go | 3 +- adapters/tpmn/tpmn.go | 6 +- adapters/tpmn/tpmn_test.go | 6 +- adapters/trafficgate/params_test.go | 2 +- adapters/trafficgate/trafficgate.go | 10 +- adapters/trafficgate/trafficgate_test.go | 6 +- adapters/triplelift/triplelift.go | 8 +- adapters/triplelift/triplelift_test.go | 6 +- .../triplelift_native/triplelift_native.go | 8 +- .../triplelift_native_test.go | 6 +- adapters/ucfunnel/params_test.go | 2 +- adapters/ucfunnel/ucfunnel.go | 8 +- adapters/ucfunnel/ucfunnel_test.go | 6 +- adapters/undertone/params_test.go | 3 +- adapters/undertone/undertone.go | 8 +- adapters/undertone/undertone_test.go | 7 +- adapters/unicorn/params_test.go | 2 +- adapters/unicorn/unicorn.go | 8 +- adapters/unicorn/unicorn_test.go | 6 +- adapters/unruly/params_test.go | 2 +- adapters/unruly/unruly.go | 8 +- adapters/unruly/unruly_test.go | 6 +- adapters/videobyte/params_test.go | 2 +- adapters/videobyte/videobyte.go | 8 +- adapters/videobyte/videobyte_test.go | 4 +- adapters/videoheroes/params_test.go | 2 +- adapters/videoheroes/videoheroes.go | 10 +- adapters/videoheroes/videoheroes_test.go | 6 +- adapters/vidoomy/params_test.go | 2 +- adapters/vidoomy/vidoomy.go | 8 +- adapters/vidoomy/vidoomy_test.go | 6 +- adapters/visiblemeasures/params_test.go | 2 +- adapters/visiblemeasures/visiblemeasures.go | 8 +- .../visiblemeasures/visiblemeasures_test.go | 6 +- adapters/visx/params_test.go | 2 +- adapters/visx/visx.go | 8 +- adapters/visx/visx_test.go | 6 +- adapters/vox/params_test.go | 2 +- adapters/vox/vox.go | 7 +- adapters/vox/vox_test.go | 6 +- adapters/vrtcal/params_test.go | 2 +- adapters/vrtcal/vrtcal.go | 8 +- adapters/vrtcal/vrtcal_test.go | 6 +- adapters/xeworks/params_test.go | 2 +- adapters/xeworks/xeworks.go | 10 +- adapters/xeworks/xeworks_test.go | 6 +- adapters/yahooAds/params_test.go | 2 +- adapters/yahooAds/yahooAds.go | 8 +- adapters/yahooAds/yahooAds_test.go | 6 +- adapters/yeahmobi/params_test.go | 3 +- adapters/yeahmobi/yeahmobi.go | 10 +- adapters/yeahmobi/yeahmobi_test.go | 6 +- adapters/yieldlab/params_test.go | 2 +- adapters/yieldlab/yieldlab.go | 8 +- adapters/yieldlab/yieldlab_test.go | 6 +- adapters/yieldmo/params_test.go | 2 +- adapters/yieldmo/yieldmo.go | 8 +- adapters/yieldmo/yieldmo_test.go | 6 +- adapters/yieldone/params_test.go | 3 +- adapters/yieldone/yieldone.go | 8 +- adapters/yieldone/yieldone_test.go | 6 +- adapters/zeroclickfraud/zeroclickfraud.go | 10 +- .../zeroclickfraud/zeroclickfraud_test.go | 6 +- adapters/zeta_global_ssp/zeta_global_ssp.go | 8 +- .../zeta_global_ssp/zeta_global_ssp_test.go | 6 +- adservertargeting/adservertargeting.go | 2 +- adservertargeting/adservertargeting_test.go | 4 +- adservertargeting/reqcache.go | 2 +- adservertargeting/requestlookup.go | 7 +- adservertargeting/requestlookup_test.go | 2 +- adservertargeting/respdataprocessor.go | 4 +- adservertargeting/respdataprocessor_test.go | 2 +- adservertargeting/utils.go | 7 +- amp/parse.go | 8 +- amp/parse_test.go | 8 +- analytics/build/build.go | 12 +- analytics/build/build_test.go | 8 +- analytics/core.go | 6 +- analytics/filesystem/file_module.go | 4 +- analytics/filesystem/file_module_test.go | 4 +- analytics/filesystem/model.go | 8 +- analytics/pubstack/configupdate.go | 2 +- analytics/pubstack/helpers/json.go | 4 +- analytics/pubstack/helpers/json_test.go | 2 +- analytics/pubstack/helpers/model.go | 8 +- analytics/pubstack/pubstack_module.go | 6 +- analytics/pubstack/pubstack_module_test.go | 4 +- analytics/runner.go | 2 +- bidadjustment/apply.go | 4 +- bidadjustment/apply_test.go | 4 +- bidadjustment/build_rules.go | 4 +- bidadjustment/build_rules_test.go | 4 +- bidadjustment/validate.go | 2 +- bidadjustment/validate_test.go | 2 +- config/account.go | 4 +- config/account_test.go | 2 +- config/bidderinfo.go | 4 +- config/bidderinfo_test.go | 2 +- config/compression.go | 2 +- config/compression_test.go | 2 +- config/config.go | 6 +- config/config_test.go | 2 +- currency/rate_converter.go | 6 +- currency/rate_converter_test.go | 2 +- currency/rates_test.go | 2 +- currency/validation.go | 4 +- currency/validation_test.go | 4 +- endpoints/cookie_sync.go | 30 +- endpoints/cookie_sync_test.go | 22 +- endpoints/currency_rates.go | 4 +- endpoints/currency_rates_test.go | 2 +- endpoints/events/account_test.go | 8 +- endpoints/events/event.go | 19 +- endpoints/events/event_test.go | 12 +- endpoints/events/vtrack.go | 18 +- endpoints/events/vtrack_test.go | 10 +- endpoints/getuids.go | 4 +- endpoints/getuids_test.go | 2 +- endpoints/info/bidders.go | 4 +- endpoints/info/bidders_detail.go | 6 +- endpoints/info/bidders_detail_test.go | 4 +- endpoints/info/bidders_test.go | 2 +- endpoints/openrtb2/amp_auction.go | 42 +- endpoints/openrtb2/amp_auction_test.go | 30 +- endpoints/openrtb2/auction.go | 54 +-- endpoints/openrtb2/auction_benchmark_test.go | 22 +- endpoints/openrtb2/auction_test.go | 32 +- endpoints/openrtb2/interstitial.go | 6 +- endpoints/openrtb2/interstitial_test.go | 2 +- endpoints/openrtb2/test_utils.go | 44 +- endpoints/openrtb2/video_auction.go | 40 +- endpoints/openrtb2/video_auction_test.go | 28 +- endpoints/setuid.go | 26 +- endpoints/setuid_test.go | 20 +- endpoints/version.go | 2 +- exchange/adapter_builders.go | 376 +++++++++--------- exchange/adapter_util.go | 8 +- exchange/adapter_util_test.go | 12 +- exchange/auction.go | 10 +- exchange/auction_response.go | 2 +- exchange/auction_test.go | 12 +- exchange/bidder.go | 26 +- exchange/bidder_test.go | 24 +- exchange/bidder_validate_bids.go | 12 +- exchange/bidder_validate_bids_test.go | 12 +- exchange/entities/entities.go | 2 +- exchange/events.go | 12 +- exchange/events_test.go | 4 +- exchange/exchange.go | 46 +-- exchange/exchange_test.go | 44 +- exchange/gdpr.go | 6 +- exchange/gdpr_test.go | 4 +- exchange/price_granularity.go | 5 +- exchange/price_granularity_test.go | 4 +- exchange/seat_non_bids.go | 4 +- exchange/seat_non_bids_test.go | 4 +- exchange/targeting.go | 2 +- exchange/targeting_test.go | 20 +- exchange/tmax_adjustments.go | 2 +- exchange/tmax_adjustments_test.go | 2 +- exchange/utils.go | 26 +- exchange/utils_test.go | 20 +- experiment/adscert/inprocesssigner.go | 5 +- experiment/adscert/remotesigner.go | 5 +- experiment/adscert/signer.go | 3 +- experiment/adscert/signer_test.go | 5 +- firstpartydata/extmerger.go | 2 +- firstpartydata/extmerger_test.go | 2 +- firstpartydata/first_party_data.go | 10 +- firstpartydata/first_party_data_test.go | 6 +- floors/enforce.go | 8 +- floors/enforce_test.go | 10 +- floors/floors.go | 6 +- floors/floors_test.go | 8 +- floors/rule.go | 4 +- floors/rule_test.go | 4 +- floors/validate.go | 4 +- floors/validate_test.go | 4 +- gdpr/aggregated_config.go | 4 +- gdpr/aggregated_config_test.go | 4 +- gdpr/basic_enforcement.go | 2 +- gdpr/basic_enforcement_test.go | 2 +- gdpr/full_enforcement.go | 2 +- gdpr/full_enforcement_test.go | 4 +- gdpr/gdpr.go | 4 +- gdpr/gdpr_test.go | 4 +- gdpr/impl.go | 2 +- gdpr/impl_test.go | 4 +- gdpr/purpose_config.go | 4 +- gdpr/purpose_config_test.go | 2 +- gdpr/purpose_enforcer.go | 4 +- gdpr/purpose_enforcer_test.go | 4 +- gdpr/signal.go | 2 +- gdpr/vendorlist-fetching.go | 2 +- gdpr/vendorlist-fetching_test.go | 4 +- go.mod | 2 +- hooks/empty_plan.go | 4 +- hooks/hookanalytics/analytics_test.go | 2 +- hooks/hookexecution/context.go | 4 +- hooks/hookexecution/enricher.go | 4 +- hooks/hookexecution/enricher_test.go | 8 +- hooks/hookexecution/errors.go | 2 +- hooks/hookexecution/execution.go | 6 +- hooks/hookexecution/executor.go | 14 +- hooks/hookexecution/executor_test.go | 18 +- hooks/hookexecution/mocks_test.go | 4 +- hooks/hookexecution/outcome.go | 2 +- hooks/hookexecution/test_utils.go | 2 +- hooks/hookstage/allprocessedbidresponses.go | 4 +- hooks/hookstage/invocation.go | 2 +- hooks/hookstage/processedauctionrequest.go | 2 +- hooks/hookstage/rawbidderresponse.go | 2 +- .../hookstage/rawbidderresponse_mutations.go | 2 +- hooks/plan.go | 4 +- hooks/plan_test.go | 6 +- hooks/repo.go | 2 +- hooks/repo_test.go | 2 +- macros/provider.go | 4 +- macros/provider_test.go | 4 +- macros/string_index_based_replacer_test.go | 4 +- main.go | 12 +- main_test.go | 2 +- metrics/config/metrics.go | 8 +- metrics/config/metrics_test.go | 6 +- metrics/go_metrics.go | 4 +- metrics/go_metrics_test.go | 4 +- metrics/metrics.go | 2 +- metrics/metrics_mock.go | 2 +- metrics/prometheus/preload.go | 4 +- metrics/prometheus/prometheus.go | 6 +- metrics/prometheus/prometheus_test.go | 6 +- modules/builder.go | 2 +- modules/generator/builder.tmpl | 2 +- modules/helpers.go | 4 +- modules/modules.go | 8 +- modules/modules_test.go | 8 +- modules/prebid/ortb2blocking/analytics.go | 4 +- modules/prebid/ortb2blocking/config.go | 2 +- .../ortb2blocking/hook_bidderrequest.go | 6 +- .../ortb2blocking/hook_raw_bidder_response.go | 6 +- modules/prebid/ortb2blocking/module.go | 4 +- modules/prebid/ortb2blocking/module_test.go | 10 +- openrtb_ext/convert_down_test.go | 2 +- openrtb_ext/deal_tier.go | 2 +- openrtb_ext/deal_tier_test.go | 2 +- openrtb_ext/device.go | 2 +- openrtb_ext/device_test.go | 2 +- openrtb_ext/imp_appnexus.go | 2 +- openrtb_ext/imp_appnexus_test.go | 2 +- openrtb_ext/imp_freewheelssp.go | 2 +- openrtb_ext/multibid_test.go | 2 +- openrtb_ext/request.go | 8 +- openrtb_ext/request_test.go | 4 +- openrtb_ext/request_wrapper.go | 8 +- openrtb_ext/request_wrapper_test.go | 4 +- openrtb_ext/site_test.go | 4 +- openrtb_ext/supplyChain.go | 2 +- openrtb_ext/supplyChain_test.go | 2 +- ortb/clone.go | 4 +- ortb/clone_test.go | 2 +- ortb/default.go | 4 +- ortb/default_test.go | 8 +- pbs/usersync.go | 6 +- prebid_cache_client/client.go | 4 +- prebid_cache_client/client_test.go | 8 +- privacy/activitycontrol.go | 4 +- privacy/activitycontrol_test.go | 6 +- privacy/ccpa/consentwriter.go | 2 +- privacy/ccpa/consentwriter_test.go | 2 +- privacy/ccpa/parsedpolicy.go | 2 +- privacy/ccpa/policy.go | 6 +- privacy/ccpa/policy_test.go | 2 +- privacy/enforcement.go | 2 +- privacy/gdpr/consentwriter.go | 2 +- privacy/lmt/ios.go | 4 +- privacy/lmt/ios_test.go | 2 +- privacy/rule_condition_test.go | 2 +- privacy/scrubber.go | 8 +- privacy/scrubber_test.go | 3 +- router/admin.go | 6 +- router/aspects/request_timeout_handler.go | 4 +- .../aspects/request_timeout_handler_test.go | 4 +- router/router.go | 54 +-- router/router_test.go | 6 +- schain/schain.go | 2 +- schain/schain_test.go | 2 +- schain/schainwriter.go | 4 +- schain/schainwriter_test.go | 4 +- scripts/check_coverage.sh | 3 +- server/listener.go | 2 +- server/listener_test.go | 4 +- server/prometheus.go | 4 +- server/server.go | 6 +- server/server_test.go | 4 +- .../backends/db_fetcher/fetcher.go | 4 +- .../backends/db_fetcher/fetcher_test.go | 2 +- .../backends/db_provider/db_provider.go | 2 +- .../backends/db_provider/db_provider_mock.go | 2 +- .../backends/db_provider/mysql_dbprovider.go | 2 +- .../db_provider/mysql_dbprovider_test.go | 2 +- .../db_provider/postgres_dbprovider.go | 2 +- .../db_provider/postgres_dbprovider_test.go | 2 +- .../backends/empty_fetcher/fetcher.go | 2 +- .../backends/file_fetcher/fetcher.go | 4 +- .../backends/file_fetcher/fetcher_test.go | 4 +- .../backends/http_fetcher/fetcher.go | 4 +- .../backends/http_fetcher/fetcher_test.go | 2 +- stored_requests/caches/cachestest/reliable.go | 2 +- stored_requests/caches/memory/cache.go | 2 +- stored_requests/caches/memory/cache_test.go | 4 +- stored_requests/config/config.go | 30 +- stored_requests/config/config_test.go | 16 +- stored_requests/events/api/api.go | 4 +- stored_requests/events/api/api_test.go | 6 +- stored_requests/events/database/database.go | 10 +- .../events/database/database_test.go | 8 +- stored_requests/events/events.go | 2 +- stored_requests/events/events_test.go | 4 +- stored_requests/events/http/http.go | 4 +- stored_requests/events/http/http_test.go | 2 +- stored_requests/fetcher.go | 2 +- stored_requests/fetcher_test.go | 4 +- stored_responses/stored_responses.go | 4 +- stored_responses/stored_responses_test.go | 2 +- usersync/chooser.go | 3 +- usersync/chooser_test.go | 9 +- usersync/cookie.go | 6 +- usersync/cookie_test.go | 4 +- usersync/decoder.go | 2 +- usersync/encoder.go | 2 +- usersync/syncer.go | 4 +- usersync/syncer_test.go | 4 +- usersync/syncersbuilder.go | 2 +- usersync/syncersbuilder_test.go | 4 +- util/httputil/httputil.go | 2 +- util/httputil/httputil_test.go | 2 +- util/jsonutil/jsonutil.go | 2 +- util/task/ticker_task_test.go | 2 +- version/xprebidheader.go | 2 +- version/xprebidheader_test.go | 4 +- 816 files changed, 2697 insertions(+), 2657 deletions(-) diff --git a/.gitignore b/.gitignore index 4e298f2a3d9..0df7cde54fd 100644 --- a/.gitignore +++ b/.gitignore @@ -30,8 +30,8 @@ vendor # build artifacts prebid-server -build -debug +/build +/debug __debug_bin # config files diff --git a/account/account.go b/account/account.go index 1f5a3feee14..2c243e0dd90 100644 --- a/account/account.go +++ b/account/account.go @@ -6,13 +6,13 @@ import ( "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // GetAccount looks up the config.Account object referenced by the given accountID, with access rules applied diff --git a/account/account_test.go b/account/account_test.go index 7a242f21188..369c2d2c40d 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -6,12 +6,12 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/iputil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index 26349e8426b..c7c3300a648 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type TtxAdapter struct { diff --git a/adapters/33across/33across_test.go b/adapters/33across/33across_test.go index bdc546a9627..c84ca0ad1d2 100644 --- a/adapters/33across/33across_test.go +++ b/adapters/33across/33across_test.go @@ -3,9 +3,9 @@ package ttx import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/33across/params_test.go b/adapters/33across/params_test.go index 2d488c4148c..ba985b2b250 100644 --- a/adapters/33across/params_test.go +++ b/adapters/33across/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/33across.json diff --git a/adapters/aax/aax.go b/adapters/aax/aax.go index 86994c6dea2..a36bf3ad37e 100644 --- a/adapters/aax/aax.go +++ b/adapters/aax/aax.go @@ -7,10 +7,10 @@ import ( "net/url" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/aax/aax_test.go b/adapters/aax/aax_test.go index 6a5eaed5dfe..c4fd1c392aa 100644 --- a/adapters/aax/aax_test.go +++ b/adapters/aax/aax_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/aax/params_test.go b/adapters/aax/params_test.go index edf9fb6fc48..bdfa46a9e63 100644 --- a/adapters/aax/params_test.go +++ b/adapters/aax/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/aax.json diff --git a/adapters/aceex/aceex.go b/adapters/aceex/aceex.go index 61863f0b8a8..a87a2b11fcf 100644 --- a/adapters/aceex/aceex.go +++ b/adapters/aceex/aceex.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/aceex/aceex_test.go b/adapters/aceex/aceex_test.go index ec0e0fec710..71c26ed0bed 100644 --- a/adapters/aceex/aceex_test.go +++ b/adapters/aceex/aceex_test.go @@ -3,9 +3,9 @@ package aceex import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/aceex/params_test.go b/adapters/aceex/params_test.go index 220adb23379..cb6445c491a 100644 --- a/adapters/aceex/params_test.go +++ b/adapters/aceex/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/acuityads/acuityads.go b/adapters/acuityads/acuityads.go index 4370beb72d1..9152e80b83f 100644 --- a/adapters/acuityads/acuityads.go +++ b/adapters/acuityads/acuityads.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AcuityAdsAdapter struct { diff --git a/adapters/acuityads/acuityads_test.go b/adapters/acuityads/acuityads_test.go index ea9d4f24352..c426d02c533 100644 --- a/adapters/acuityads/acuityads_test.go +++ b/adapters/acuityads/acuityads_test.go @@ -3,9 +3,9 @@ package acuityads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/acuityads/params_test.go b/adapters/acuityads/params_test.go index 892fe9a646d..3c7b3a97914 100644 --- a/adapters/acuityads/params_test.go +++ b/adapters/acuityads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index 39b1e945f7d..d14ff015ced 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -12,9 +12,9 @@ import ( "github.com/mitchellh/copystructure" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" diff --git a/adapters/adf/adf.go b/adapters/adf/adf.go index 7ff817559cc..0f14a05e947 100644 --- a/adapters/adf/adf.go +++ b/adapters/adf/adf.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/adf/adf_test.go b/adapters/adf/adf_test.go index bf8af8d6845..20e4f3dde32 100644 --- a/adapters/adf/adf_test.go +++ b/adapters/adf/adf_test.go @@ -3,9 +3,9 @@ package adf import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adf/params_test.go b/adapters/adf/params_test.go index 0b05519df3b..dc0d84927bc 100644 --- a/adapters/adf/params_test.go +++ b/adapters/adf/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adf.json diff --git a/adapters/adgeneration/adgeneration.go b/adapters/adgeneration/adgeneration.go index a2a10ed51f2..88166ec237d 100644 --- a/adapters/adgeneration/adgeneration.go +++ b/adapters/adgeneration/adgeneration.go @@ -11,10 +11,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AdgenerationAdapter struct { diff --git a/adapters/adgeneration/adgeneration_test.go b/adapters/adgeneration/adgeneration_test.go index c204fbd320d..3e94ac6c382 100644 --- a/adapters/adgeneration/adgeneration_test.go +++ b/adapters/adgeneration/adgeneration_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adgeneration/params_test.go b/adapters/adgeneration/params_test.go index 062d122ac08..58400e96656 100644 --- a/adapters/adgeneration/params_test.go +++ b/adapters/adgeneration/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adhese/adhese.go b/adapters/adhese/adhese.go index 9c84676c379..56d60be2505 100644 --- a/adapters/adhese/adhese.go +++ b/adapters/adhese/adhese.go @@ -11,11 +11,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AdheseAdapter struct { diff --git a/adapters/adhese/adhese_test.go b/adapters/adhese/adhese_test.go index d09a29ee9bd..2b70bb001a6 100644 --- a/adapters/adhese/adhese_test.go +++ b/adapters/adhese/adhese_test.go @@ -3,9 +3,9 @@ package adhese import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adhese/params_test.go b/adapters/adhese/params_test.go index 45024749b2d..1a0aa381cb1 100644 --- a/adapters/adhese/params_test.go +++ b/adapters/adhese/params_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adkernel/adkernel.go b/adapters/adkernel/adkernel.go index 5fae001d7dd..e8ccc360e0e 100644 --- a/adapters/adkernel/adkernel.go +++ b/adapters/adkernel/adkernel.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adkernelAdapter struct { diff --git a/adapters/adkernel/adkernel_test.go b/adapters/adkernel/adkernel_test.go index ae35f712400..2639eb25624 100644 --- a/adapters/adkernel/adkernel_test.go +++ b/adapters/adkernel/adkernel_test.go @@ -3,9 +3,9 @@ package adkernel import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adkernelAdn/adkernelAdn.go b/adapters/adkernelAdn/adkernelAdn.go index 45e9e41c10c..218708cb697 100644 --- a/adapters/adkernelAdn/adkernelAdn.go +++ b/adapters/adkernelAdn/adkernelAdn.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adkernelAdnAdapter struct { diff --git a/adapters/adkernelAdn/adkernelAdn_test.go b/adapters/adkernelAdn/adkernelAdn_test.go index 651d82be3b6..e43d00bf0bf 100644 --- a/adapters/adkernelAdn/adkernelAdn_test.go +++ b/adapters/adkernelAdn/adkernelAdn_test.go @@ -3,9 +3,9 @@ package adkernelAdn import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adman/adman.go b/adapters/adman/adman.go index 5350fa7cb86..48a4dff961c 100644 --- a/adapters/adman/adman.go +++ b/adapters/adman/adman.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // AdmanAdapter struct diff --git a/adapters/adman/adman_test.go b/adapters/adman/adman_test.go index 608232cc4b8..5617035c713 100644 --- a/adapters/adman/adman_test.go +++ b/adapters/adman/adman_test.go @@ -3,9 +3,9 @@ package adman import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adman/params_test.go b/adapters/adman/params_test.go index a80c2a44b8b..9d7e0c16d51 100644 --- a/adapters/adman/params_test.go +++ b/adapters/adman/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the adman schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/admixer/admixer.go b/adapters/admixer/admixer.go index 9a07a8922a9..5c68518ee52 100644 --- a/adapters/admixer/admixer.go +++ b/adapters/admixer/admixer.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AdmixerAdapter struct { diff --git a/adapters/admixer/admixer_test.go b/adapters/admixer/admixer_test.go index 766f890cdf7..5985d4303c9 100644 --- a/adapters/admixer/admixer_test.go +++ b/adapters/admixer/admixer_test.go @@ -3,9 +3,9 @@ package admixer import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/admixer/params_test.go b/adapters/admixer/params_test.go index bfa75a4884f..af85569b460 100644 --- a/adapters/admixer/params_test.go +++ b/adapters/admixer/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/admixer.json diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go index fd667ddc506..cb8d876fb53 100644 --- a/adapters/adnuntius/adnuntius.go +++ b/adapters/adnuntius/adnuntius.go @@ -10,11 +10,11 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/timeutil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/timeutil" ) type QueryString map[string]string diff --git a/adapters/adnuntius/adnuntius_test.go b/adapters/adnuntius/adnuntius_test.go index 9c431c2a315..f6edb313708 100644 --- a/adapters/adnuntius/adnuntius_test.go +++ b/adapters/adnuntius/adnuntius_test.go @@ -4,10 +4,10 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adnuntius/params_test.go b/adapters/adnuntius/params_test.go index c3b42018340..259b5145801 100644 --- a/adapters/adnuntius/params_test.go +++ b/adapters/adnuntius/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adnuntius.json diff --git a/adapters/adocean/adocean.go b/adapters/adocean/adocean.go index a4e6223be6d..aa9446d76f0 100644 --- a/adapters/adocean/adocean.go +++ b/adapters/adocean/adocean.go @@ -14,11 +14,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const adapterVersion = "1.3.0" diff --git a/adapters/adocean/adocean_test.go b/adapters/adocean/adocean_test.go index 8d646cb9ca0..d7fe33b8de5 100644 --- a/adapters/adocean/adocean_test.go +++ b/adapters/adocean/adocean_test.go @@ -3,9 +3,9 @@ package adocean import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adocean/params_test.go b/adapters/adocean/params_test.go index 18625b5e85e..f35dc1aaf87 100644 --- a/adapters/adocean/params_test.go +++ b/adapters/adocean/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adoppler/adoppler.go b/adapters/adoppler/adoppler.go index 90070e8145d..42661206ea6 100644 --- a/adapters/adoppler/adoppler.go +++ b/adapters/adoppler/adoppler.go @@ -9,11 +9,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const DefaultClient = "app" diff --git a/adapters/adoppler/adoppler_test.go b/adapters/adoppler/adoppler_test.go index 9f026b2f29c..d05118c4800 100644 --- a/adapters/adoppler/adoppler_test.go +++ b/adapters/adoppler/adoppler_test.go @@ -3,9 +3,9 @@ package adoppler import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adot/adot.go b/adapters/adot/adot.go index fbba9fee467..39c665b4f12 100644 --- a/adapters/adot/adot.go +++ b/adapters/adot/adot.go @@ -8,10 +8,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/adot/adot_test.go b/adapters/adot/adot_test.go index 2f35f2a85fa..1d2de9fa8ea 100644 --- a/adapters/adot/adot_test.go +++ b/adapters/adot/adot_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adot/params_test.go b/adapters/adot/params_test.go index 6760419b470..a47ff2ff4eb 100644 --- a/adapters/adot/params_test.go +++ b/adapters/adot/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adot.json diff --git a/adapters/adpone/adpone.go b/adapters/adpone/adpone.go index bf7ffb93992..77c7c8e0858 100644 --- a/adapters/adpone/adpone.go +++ b/adapters/adpone/adpone.go @@ -6,11 +6,11 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/errortypes" ) // Builder builds a new instance of the Adpone adapter for the given bidder with the given config. diff --git a/adapters/adpone/adpone_test.go b/adapters/adpone/adpone_test.go index 7b01a382587..bd4f5ed514e 100644 --- a/adapters/adpone/adpone_test.go +++ b/adapters/adpone/adpone_test.go @@ -3,9 +3,9 @@ package adpone import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsDir = "adponetest" diff --git a/adapters/adprime/adprime.go b/adapters/adprime/adprime.go index 836abe26d83..56193c9133b 100644 --- a/adapters/adprime/adprime.go +++ b/adapters/adprime/adprime.go @@ -7,10 +7,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // AdprimeAdapter struct diff --git a/adapters/adprime/adprime_test.go b/adapters/adprime/adprime_test.go index e5cf7df8df5..34dde15d1ba 100644 --- a/adapters/adprime/adprime_test.go +++ b/adapters/adprime/adprime_test.go @@ -3,9 +3,9 @@ package adprime import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adprime/params_test.go b/adapters/adprime/params_test.go index b466c658ede..67bbebf0086 100644 --- a/adapters/adprime/params_test.go +++ b/adapters/adprime/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the adprime schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/adquery/adquery.go b/adapters/adquery/adquery.go index 6a7dafa0ccb..92a1733dae6 100644 --- a/adapters/adquery/adquery.go +++ b/adapters/adquery/adquery.go @@ -9,10 +9,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/adquery/adquery_test.go b/adapters/adquery/adquery_test.go index 228d835d6c4..2b88c3b82c7 100644 --- a/adapters/adquery/adquery_test.go +++ b/adapters/adquery/adquery_test.go @@ -1,10 +1,11 @@ package adquery import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adquery/params_test.go b/adapters/adquery/params_test.go index cba021007d3..e72b0b02ea3 100644 --- a/adapters/adquery/params_test.go +++ b/adapters/adquery/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adquery/types.go b/adapters/adquery/types.go index e46afaea63e..5013ecf488a 100644 --- a/adapters/adquery/types.go +++ b/adapters/adquery/types.go @@ -1,6 +1,6 @@ package adquery -import "github.com/prebid/prebid-server/openrtb_ext" +import "github.com/prebid/prebid-server/v2/openrtb_ext" type BidderRequest struct { V string `json:"v"` diff --git a/adapters/adrino/adrino.go b/adapters/adrino/adrino.go index a63ad9beef6..44de44f5cf8 100644 --- a/adapters/adrino/adrino.go +++ b/adapters/adrino/adrino.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/adrino/adrino_test.go b/adapters/adrino/adrino_test.go index 7566f3ed499..e969868c135 100644 --- a/adapters/adrino/adrino_test.go +++ b/adapters/adrino/adrino_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adrino/params_test.go b/adapters/adrino/params_test.go index f82f08ce9e0..0ad36e6e4d6 100644 --- a/adapters/adrino/params_test.go +++ b/adapters/adrino/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adrino.json diff --git a/adapters/adsinteractive/adsinteractive.go b/adapters/adsinteractive/adsinteractive.go index 04edf774b80..d50dad0aabd 100644 --- a/adapters/adsinteractive/adsinteractive.go +++ b/adapters/adsinteractive/adsinteractive.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/adsinteractive/adsinteractive_test.go b/adapters/adsinteractive/adsinteractive_test.go index 9a1397b799f..bed577c6003 100644 --- a/adapters/adsinteractive/adsinteractive_test.go +++ b/adapters/adsinteractive/adsinteractive_test.go @@ -3,9 +3,9 @@ package adsinteractive import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsDir = "adsinteractivetest" diff --git a/adapters/adsinteractive/params_test.go b/adapters/adsinteractive/params_test.go index 2561fc864da..caff03a2697 100644 --- a/adapters/adsinteractive/params_test.go +++ b/adapters/adsinteractive/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adsinteractive.json diff --git a/adapters/adtarget/adtarget.go b/adapters/adtarget/adtarget.go index 00f797eccf8..ccd362830fc 100644 --- a/adapters/adtarget/adtarget.go +++ b/adapters/adtarget/adtarget.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AdtargetAdapter struct { diff --git a/adapters/adtarget/adtarget_test.go b/adapters/adtarget/adtarget_test.go index 2ee45041b09..2813ea2c195 100644 --- a/adapters/adtarget/adtarget_test.go +++ b/adapters/adtarget/adtarget_test.go @@ -3,9 +3,9 @@ package adtarget import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adtarget/params_test.go b/adapters/adtarget/params_test.go index 4c39639fb7b..d0993215086 100644 --- a/adapters/adtarget/params_test.go +++ b/adapters/adtarget/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adtarget.json diff --git a/adapters/adtelligent/adtelligent.go b/adapters/adtelligent/adtelligent.go index e2f5ef82cab..281d79233a0 100644 --- a/adapters/adtelligent/adtelligent.go +++ b/adapters/adtelligent/adtelligent.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AdtelligentAdapter struct { diff --git a/adapters/adtelligent/adtelligent_test.go b/adapters/adtelligent/adtelligent_test.go index 948710387b3..905ce013840 100644 --- a/adapters/adtelligent/adtelligent_test.go +++ b/adapters/adtelligent/adtelligent_test.go @@ -3,9 +3,9 @@ package adtelligent import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adtelligent/params_test.go b/adapters/adtelligent/params_test.go index 227920b25b4..f86a7641af9 100644 --- a/adapters/adtelligent/params_test.go +++ b/adapters/adtelligent/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adtelligent.json diff --git a/adapters/adtrgtme/adtrgtme.go b/adapters/adtrgtme/adtrgtme.go index 254bf5051e9..47feaceefa7 100644 --- a/adapters/adtrgtme/adtrgtme.go +++ b/adapters/adtrgtme/adtrgtme.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/adtrgtme/adtrgtme_test.go b/adapters/adtrgtme/adtrgtme_test.go index 91d9b233ffe..07bfea3c652 100644 --- a/adapters/adtrgtme/adtrgtme_test.go +++ b/adapters/adtrgtme/adtrgtme_test.go @@ -3,9 +3,9 @@ package adtrgtme import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adtrgtme/params_test.go b/adapters/adtrgtme/params_test.go index e89f8423ed4..4745c323887 100644 --- a/adapters/adtrgtme/params_test.go +++ b/adapters/adtrgtme/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/advangelists/advangelists.go b/adapters/advangelists/advangelists.go index 19bb4c326e2..c779483d64a 100644 --- a/adapters/advangelists/advangelists.go +++ b/adapters/advangelists/advangelists.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AdvangelistsAdapter struct { diff --git a/adapters/advangelists/advangelists_test.go b/adapters/advangelists/advangelists_test.go index e4c5debaa79..5165ef1f3a7 100644 --- a/adapters/advangelists/advangelists_test.go +++ b/adapters/advangelists/advangelists_test.go @@ -3,9 +3,9 @@ package advangelists import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/advangelists/params_test.go b/adapters/advangelists/params_test.go index a58217a0ffd..966967ba312 100644 --- a/adapters/advangelists/params_test.go +++ b/adapters/advangelists/params_test.go @@ -2,8 +2,9 @@ package advangelists import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adview/adview.go b/adapters/adview/adview.go index d2181b6591a..9937b6ae3ee 100644 --- a/adapters/adview/adview.go +++ b/adapters/adview/adview.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/adview/adview_test.go b/adapters/adview/adview_test.go index d0c993cfb56..2045586e97d 100644 --- a/adapters/adview/adview_test.go +++ b/adapters/adview/adview_test.go @@ -3,9 +3,9 @@ package adview import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adview/params_test.go b/adapters/adview/params_test.go index 6d124e9b556..d5e498645e0 100644 --- a/adapters/adview/params_test.go +++ b/adapters/adview/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adxcg/adxcg.go b/adapters/adxcg/adxcg.go index 6b489d322b0..a8cb380f6c0 100644 --- a/adapters/adxcg/adxcg.go +++ b/adapters/adxcg/adxcg.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Builder builds a new instance of the Adxcg adapter for the given bidder with the given config. diff --git a/adapters/adxcg/adxcg_test.go b/adapters/adxcg/adxcg_test.go index aa5f955c372..f117f7b2ba1 100644 --- a/adapters/adxcg/adxcg_test.go +++ b/adapters/adxcg/adxcg_test.go @@ -3,9 +3,9 @@ package adxcg import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsDir = "adxcgtest" diff --git a/adapters/adyoulike/adyoulike.go b/adapters/adyoulike/adyoulike.go index e00e95dccb5..72e6612b675 100644 --- a/adapters/adyoulike/adyoulike.go +++ b/adapters/adyoulike/adyoulike.go @@ -8,10 +8,10 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { diff --git a/adapters/adyoulike/adyoulike_test.go b/adapters/adyoulike/adyoulike_test.go index d3000f673fc..2cf7a2b49ec 100644 --- a/adapters/adyoulike/adyoulike_test.go +++ b/adapters/adyoulike/adyoulike_test.go @@ -3,9 +3,9 @@ package adyoulike import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsBidderEndpoint = "https://localhost/bid/4" diff --git a/adapters/adyoulike/params_test.go b/adapters/adyoulike/params_test.go index 8aebaf2844e..6eb9e09a3cb 100644 --- a/adapters/adyoulike/params_test.go +++ b/adapters/adyoulike/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adyoulike.json diff --git a/adapters/aidem/aidem.go b/adapters/aidem/aidem.go index 9748f32c957..dc8d42a07ed 100644 --- a/adapters/aidem/aidem.go +++ b/adapters/aidem/aidem.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/aidem/aidem_test.go b/adapters/aidem/aidem_test.go index 03bcc7e0fb5..8cf6e3d16a2 100644 --- a/adapters/aidem/aidem_test.go +++ b/adapters/aidem/aidem_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/aidem/params_test.go b/adapters/aidem/params_test.go index 36190c0bc9f..4d2c5c14cbc 100644 --- a/adapters/aidem/params_test.go +++ b/adapters/aidem/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/aidem.json TODO: MUST BE CREATED diff --git a/adapters/aja/aja.go b/adapters/aja/aja.go index 5a8afb00045..178c7f6ca68 100644 --- a/adapters/aja/aja.go +++ b/adapters/aja/aja.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AJAAdapter struct { diff --git a/adapters/aja/aja_test.go b/adapters/aja/aja_test.go index 75e35bedeb0..de4f1d13dab 100644 --- a/adapters/aja/aja_test.go +++ b/adapters/aja/aja_test.go @@ -3,9 +3,9 @@ package aja import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsBidderEndpoint = "https://localhost/bid/4" diff --git a/adapters/algorix/algorix.go b/adapters/algorix/algorix.go index 07f2b123389..e646467f744 100644 --- a/adapters/algorix/algorix.go +++ b/adapters/algorix/algorix.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/algorix/algorix_test.go b/adapters/algorix/algorix_test.go index 762b00dcee4..a401713d290 100644 --- a/adapters/algorix/algorix_test.go +++ b/adapters/algorix/algorix_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/algorix/params_test.go b/adapters/algorix/params_test.go index 227b9e0a6d4..7017a43c730 100644 --- a/adapters/algorix/params_test.go +++ b/adapters/algorix/params_test.go @@ -2,8 +2,9 @@ package algorix import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/amx/amx.go b/adapters/amx/amx.go index 6b4ba20b4fd..8ff30baef79 100644 --- a/adapters/amx/amx.go +++ b/adapters/amx/amx.go @@ -7,10 +7,10 @@ import ( "net/url" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const nbrHeaderName = "x-nbr" diff --git a/adapters/amx/amx_test.go b/adapters/amx/amx_test.go index 9b068e5aa82..7286f221997 100644 --- a/adapters/amx/amx_test.go +++ b/adapters/amx/amx_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" ) const ( diff --git a/adapters/amx/params_test.go b/adapters/amx/params_test.go index 89e9a3adeb4..4d9d646e426 100644 --- a/adapters/amx/params_test.go +++ b/adapters/amx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/apacdex/apacdex.go b/adapters/apacdex/apacdex.go index fdf30f4061f..ee10247638c 100644 --- a/adapters/apacdex/apacdex.go +++ b/adapters/apacdex/apacdex.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/apacdex/apacdex_test.go b/adapters/apacdex/apacdex_test.go index 1435850e3eb..ba5f1e7a3e6 100644 --- a/adapters/apacdex/apacdex_test.go +++ b/adapters/apacdex/apacdex_test.go @@ -3,9 +3,9 @@ package apacdex import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/apacdex/params_test.go b/adapters/apacdex/params_test.go index 07d846506e5..a7dcf7bcb04 100644 --- a/adapters/apacdex/params_test.go +++ b/adapters/apacdex/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/apacdex.json diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index ddba6a58de7..ad3be2dbb2d 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -12,15 +12,15 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/util/maputil" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/randomutil" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/util/maputil" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/randomutil" + + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go index bb04b61bb97..72937b25927 100644 --- a/adapters/appnexus/appnexus_test.go +++ b/adapters/appnexus/appnexus_test.go @@ -4,9 +4,9 @@ import ( "net/url" "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/adapters/appnexus/params_test.go b/adapters/appnexus/params_test.go index ed9adedcc6b..12e43f8659b 100644 --- a/adapters/appnexus/params_test.go +++ b/adapters/appnexus/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/appnexus.json diff --git a/adapters/appush/appush.go b/adapters/appush/appush.go index 18b294be7f0..36eccef0a0c 100644 --- a/adapters/appush/appush.go +++ b/adapters/appush/appush.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/appush/appush_test.go b/adapters/appush/appush_test.go index 65be1c32f0a..92f458f1525 100644 --- a/adapters/appush/appush_test.go +++ b/adapters/appush/appush_test.go @@ -3,9 +3,9 @@ package appush import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/appush/params_test.go b/adapters/appush/params_test.go index a471c3f0300..725882cb2ac 100644 --- a/adapters/appush/params_test.go +++ b/adapters/appush/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index 753b8a2cf70..8a288bb0338 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -13,12 +13,12 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/maputil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/maputil" ) var supportedBannerHeights = map[int64]struct{}{ diff --git a/adapters/audienceNetwork/facebook_test.go b/adapters/audienceNetwork/facebook_test.go index 27af5506ab4..e2320c1eaea 100644 --- a/adapters/audienceNetwork/facebook_test.go +++ b/adapters/audienceNetwork/facebook_test.go @@ -4,10 +4,10 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/automatad/automatad.go b/adapters/automatad/automatad.go index 75f1dcf3b5a..3f74d290245 100644 --- a/adapters/automatad/automatad.go +++ b/adapters/automatad/automatad.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/automatad/automatad_test.go b/adapters/automatad/automatad_test.go index 914692741d1..790b2b42c67 100644 --- a/adapters/automatad/automatad_test.go +++ b/adapters/automatad/automatad_test.go @@ -3,9 +3,9 @@ package automatad import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/automatad/params_test.go b/adapters/automatad/params_test.go index df7f676b70a..468624c1eb3 100644 --- a/adapters/automatad/params_test.go +++ b/adapters/automatad/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/avocet/avocet.go b/adapters/avocet/avocet.go index 888fcab0fe2..b50b626792e 100644 --- a/adapters/avocet/avocet.go +++ b/adapters/avocet/avocet.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // AvocetAdapter implements a adapters.Bidder compatible with the Avocet advertising platform. diff --git a/adapters/avocet/avocet_test.go b/adapters/avocet/avocet_test.go index 1dcf81c7d78..a7e01cdb65d 100644 --- a/adapters/avocet/avocet_test.go +++ b/adapters/avocet/avocet_test.go @@ -8,11 +8,11 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/axis/axis.go b/adapters/axis/axis.go index f03a572e625..afda6045a1e 100644 --- a/adapters/axis/axis.go +++ b/adapters/axis/axis.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/axis/axis_test.go b/adapters/axis/axis_test.go index 56cb7f0b6e8..451141d8458 100644 --- a/adapters/axis/axis_test.go +++ b/adapters/axis/axis_test.go @@ -3,9 +3,9 @@ package axis import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/axis/params_test.go b/adapters/axis/params_test.go index ba374c80b0f..1c0391d6954 100644 --- a/adapters/axis/params_test.go +++ b/adapters/axis/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/axonix/axonix.go b/adapters/axonix/axonix.go index 2289050b462..d5d0b0a41ee 100644 --- a/adapters/axonix/axonix.go +++ b/adapters/axonix/axonix.go @@ -10,11 +10,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/axonix/axonix_test.go b/adapters/axonix/axonix_test.go index aa7fdf96b7c..9634a54fe65 100644 --- a/adapters/axonix/axonix_test.go +++ b/adapters/axonix/axonix_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/axonix/params_test.go b/adapters/axonix/params_test.go index d44fcd15e88..59b62220746 100644 --- a/adapters/axonix/params_test.go +++ b/adapters/axonix/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/axonix.json diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index 51e1d02d026..2eba35b91cb 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -10,10 +10,10 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const Seat = "beachfront" diff --git a/adapters/beachfront/beachfront_test.go b/adapters/beachfront/beachfront_test.go index 3e1f75d6c41..23c4dcf6a9e 100644 --- a/adapters/beachfront/beachfront_test.go +++ b/adapters/beachfront/beachfront_test.go @@ -3,9 +3,9 @@ package beachfront import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/beachfront/params_test.go b/adapters/beachfront/params_test.go index a387a2f0af3..23e9ae7f492 100644 --- a/adapters/beachfront/params_test.go +++ b/adapters/beachfront/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/beintoo/beintoo.go b/adapters/beintoo/beintoo.go index 9fc51d6a3df..b4e7626a6f6 100644 --- a/adapters/beintoo/beintoo.go +++ b/adapters/beintoo/beintoo.go @@ -8,10 +8,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type BeintooAdapter struct { diff --git a/adapters/beintoo/beintoo_test.go b/adapters/beintoo/beintoo_test.go index f95be60169b..11ef7ec9e7b 100644 --- a/adapters/beintoo/beintoo_test.go +++ b/adapters/beintoo/beintoo_test.go @@ -3,9 +3,9 @@ package beintoo import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/beintoo/params_test.go b/adapters/beintoo/params_test.go index b92b2a108dd..0392e08bb9b 100644 --- a/adapters/beintoo/params_test.go +++ b/adapters/beintoo/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/bematterfull/bematterfull.go b/adapters/bematterfull/bematterfull.go index bf63e7c8c8b..61b98d771ba 100644 --- a/adapters/bematterfull/bematterfull.go +++ b/adapters/bematterfull/bematterfull.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type bidType struct { diff --git a/adapters/bematterfull/bematterfulltest_test.go b/adapters/bematterfull/bematterfulltest_test.go index f3d1f412761..36198bc4f2a 100644 --- a/adapters/bematterfull/bematterfulltest_test.go +++ b/adapters/bematterfull/bematterfulltest_test.go @@ -3,9 +3,9 @@ package bematterfull import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/bematterfull/params_test.go b/adapters/bematterfull/params_test.go index 5983dd3e375..a093edbe6ac 100644 --- a/adapters/bematterfull/params_test.go +++ b/adapters/bematterfull/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/between/between.go b/adapters/between/between.go index 6e313374c86..9629f747657 100644 --- a/adapters/between/between.go +++ b/adapters/between/between.go @@ -9,11 +9,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type BetweenAdapter struct { diff --git a/adapters/between/between_test.go b/adapters/between/between_test.go index 453a331794f..332ba00b5d0 100644 --- a/adapters/between/between_test.go +++ b/adapters/between/between_test.go @@ -3,9 +3,9 @@ package between import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/between/params_test.go b/adapters/between/params_test.go index 1907084f9be..5772c218bc0 100644 --- a/adapters/between/params_test.go +++ b/adapters/between/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/between.json diff --git a/adapters/beyondmedia/beyondmedia.go b/adapters/beyondmedia/beyondmedia.go index 57674d58d52..50091aba03b 100644 --- a/adapters/beyondmedia/beyondmedia.go +++ b/adapters/beyondmedia/beyondmedia.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/beyondmedia/beyondmedia_test.go b/adapters/beyondmedia/beyondmedia_test.go index 9b22d1bb5d1..1f828e50b3a 100644 --- a/adapters/beyondmedia/beyondmedia_test.go +++ b/adapters/beyondmedia/beyondmedia_test.go @@ -3,9 +3,9 @@ package beyondmedia import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/beyondmedia/params_test.go b/adapters/beyondmedia/params_test.go index 9b0f8e8f6bb..8796b200ede 100644 --- a/adapters/beyondmedia/params_test.go +++ b/adapters/beyondmedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/bidder.go b/adapters/bidder.go index bb7424f3e00..b56f02e5156 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Bidder describes how to connect to external demand. diff --git a/adapters/bidmachine/bidmachine.go b/adapters/bidmachine/bidmachine.go index 3b760a3d530..da46a784625 100644 --- a/adapters/bidmachine/bidmachine.go +++ b/adapters/bidmachine/bidmachine.go @@ -12,11 +12,11 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/bidmachine/bidmachine_test.go b/adapters/bidmachine/bidmachine_test.go index 8f32a2fc60d..282241c3d72 100644 --- a/adapters/bidmachine/bidmachine_test.go +++ b/adapters/bidmachine/bidmachine_test.go @@ -3,9 +3,9 @@ package bidmachine import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/bidmachine/params_test.go b/adapters/bidmachine/params_test.go index 15ebbcbf128..d396ee09cd3 100644 --- a/adapters/bidmachine/params_test.go +++ b/adapters/bidmachine/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/bidmyadz/bidmyadz.go b/adapters/bidmyadz/bidmyadz.go index 42af572c391..55b2da54e44 100644 --- a/adapters/bidmyadz/bidmyadz.go +++ b/adapters/bidmyadz/bidmyadz.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/bidmyadz/bidmyadz_test.go b/adapters/bidmyadz/bidmyadz_test.go index 1eb3ef67328..41badb559af 100644 --- a/adapters/bidmyadz/bidmyadz_test.go +++ b/adapters/bidmyadz/bidmyadz_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/bidmyadz/params_test.go b/adapters/bidmyadz/params_test.go index 857cde86d22..262dcf0880d 100644 --- a/adapters/bidmyadz/params_test.go +++ b/adapters/bidmyadz/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/bidscube/bidscube.go b/adapters/bidscube/bidscube.go index e654abb5564..a1bd72c171b 100644 --- a/adapters/bidscube/bidscube.go +++ b/adapters/bidscube/bidscube.go @@ -9,10 +9,10 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/bidscube/bidscube_test.go b/adapters/bidscube/bidscube_test.go index 137ba061f65..e64ccdbef63 100644 --- a/adapters/bidscube/bidscube_test.go +++ b/adapters/bidscube/bidscube_test.go @@ -3,9 +3,9 @@ package bidscube import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/bidscube/params_test.go b/adapters/bidscube/params_test.go index ff8ade9283c..9337c4dfb40 100644 --- a/adapters/bidscube/params_test.go +++ b/adapters/bidscube/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the bidscube schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/bidstack/bidstack.go b/adapters/bidstack/bidstack.go index c5c7f5b4865..a88b503a0f8 100644 --- a/adapters/bidstack/bidstack.go +++ b/adapters/bidstack/bidstack.go @@ -9,9 +9,9 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/bidstack/bidstack_test.go b/adapters/bidstack/bidstack_test.go index f1d476459bc..d6050bdad96 100644 --- a/adapters/bidstack/bidstack_test.go +++ b/adapters/bidstack/bidstack_test.go @@ -7,9 +7,9 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/bidstack/params_test.go b/adapters/bidstack/params_test.go index 832d6000f4c..d7dc18e4a03 100644 --- a/adapters/bidstack/params_test.go +++ b/adapters/bidstack/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/bizzclick/bizzclick.go b/adapters/bizzclick/bizzclick.go index 7f65f903b4b..fee2829dd6f 100644 --- a/adapters/bizzclick/bizzclick.go +++ b/adapters/bizzclick/bizzclick.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/bizzclick/bizzclick_test.go b/adapters/bizzclick/bizzclick_test.go index 91a9fcd1da4..971f06ae70f 100644 --- a/adapters/bizzclick/bizzclick_test.go +++ b/adapters/bizzclick/bizzclick_test.go @@ -3,9 +3,9 @@ package bizzclick import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/bizzclick/params_test.go b/adapters/bizzclick/params_test.go index 34211b006f5..496643a506f 100644 --- a/adapters/bizzclick/params_test.go +++ b/adapters/bizzclick/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/bliink/bliink.go b/adapters/bliink/bliink.go index 017b69bc683..bfd8aeb1dcc 100644 --- a/adapters/bliink/bliink.go +++ b/adapters/bliink/bliink.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/bliink/bliink_test.go b/adapters/bliink/bliink_test.go index a57a82e1765..02cafb37a4b 100644 --- a/adapters/bliink/bliink_test.go +++ b/adapters/bliink/bliink_test.go @@ -3,9 +3,9 @@ package bliink import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/bliink/params_test.go b/adapters/bliink/params_test.go index dc45a3249fe..d1066e8c4ed 100644 --- a/adapters/bliink/params_test.go +++ b/adapters/bliink/params_test.go @@ -2,8 +2,9 @@ package bliink import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/blue/blue.go b/adapters/blue/blue.go index ae896d00760..d1743378c0b 100644 --- a/adapters/blue/blue.go +++ b/adapters/blue/blue.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/blue/blue_test.go b/adapters/blue/blue_test.go index d9febec66af..695984a3383 100644 --- a/adapters/blue/blue_test.go +++ b/adapters/blue/blue_test.go @@ -3,9 +3,9 @@ package blue import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestBidderBlue(t *testing.T) { diff --git a/adapters/bluesea/bluesea.go b/adapters/bluesea/bluesea.go index 44745ef00e8..7d8ff7d92b2 100644 --- a/adapters/bluesea/bluesea.go +++ b/adapters/bluesea/bluesea.go @@ -7,10 +7,10 @@ import ( "net/url" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/bluesea/bluesea_test.go b/adapters/bluesea/bluesea_test.go index 6b5ad16eb85..8bfe42ce150 100644 --- a/adapters/bluesea/bluesea_test.go +++ b/adapters/bluesea/bluesea_test.go @@ -3,9 +3,9 @@ package bluesea import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/bluesea/params_test.go b/adapters/bluesea/params_test.go index 62f14ee8c57..e1f7661ac09 100644 --- a/adapters/bluesea/params_test.go +++ b/adapters/bluesea/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the bluesea schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/bmtm/brightmountainmedia.go b/adapters/bmtm/brightmountainmedia.go index 591f03b4c1c..90bac4ac8ae 100644 --- a/adapters/bmtm/brightmountainmedia.go +++ b/adapters/bmtm/brightmountainmedia.go @@ -7,10 +7,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/bmtm/brightmountainmedia_test.go b/adapters/bmtm/brightmountainmedia_test.go index 0455c8b99b6..f98cbde0063 100644 --- a/adapters/bmtm/brightmountainmedia_test.go +++ b/adapters/bmtm/brightmountainmedia_test.go @@ -3,9 +3,9 @@ package bmtm import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/bmtm/params_test.go b/adapters/bmtm/params_test.go index 8b196f1eec8..d4a07e5a294 100644 --- a/adapters/bmtm/params_test.go +++ b/adapters/bmtm/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/boldwin/boldwin.go b/adapters/boldwin/boldwin.go index 4d88ff01458..9425cd80fbb 100644 --- a/adapters/boldwin/boldwin.go +++ b/adapters/boldwin/boldwin.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/boldwin/boldwin_test.go b/adapters/boldwin/boldwin_test.go index 33d5c68f8de..a8921a9d27f 100644 --- a/adapters/boldwin/boldwin_test.go +++ b/adapters/boldwin/boldwin_test.go @@ -3,9 +3,9 @@ package boldwin import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/boldwin/params_test.go b/adapters/boldwin/params_test.go index f3855746160..2cef0d59cd6 100644 --- a/adapters/boldwin/params_test.go +++ b/adapters/boldwin/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/brave/brave.go b/adapters/brave/brave.go index b1270b6a866..bde07b66641 100755 --- a/adapters/brave/brave.go +++ b/adapters/brave/brave.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/brave/brave_test.go b/adapters/brave/brave_test.go index e8d0a474be9..e4f58252425 100644 --- a/adapters/brave/brave_test.go +++ b/adapters/brave/brave_test.go @@ -3,9 +3,9 @@ package brave import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/brave/params_test.go b/adapters/brave/params_test.go index 1120a13c2f9..c4bcd99959b 100644 --- a/adapters/brave/params_test.go +++ b/adapters/brave/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/cadent_aperture_mx/cadentaperturemx.go b/adapters/cadent_aperture_mx/cadentaperturemx.go index 5df000bf547..4b4ccc6b2e4 100644 --- a/adapters/cadent_aperture_mx/cadentaperturemx.go +++ b/adapters/cadent_aperture_mx/cadentaperturemx.go @@ -11,10 +11,10 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/cadent_aperture_mx/cadentaperturemx_test.go b/adapters/cadent_aperture_mx/cadentaperturemx_test.go index 60034322797..961459a2ea8 100644 --- a/adapters/cadent_aperture_mx/cadentaperturemx_test.go +++ b/adapters/cadent_aperture_mx/cadentaperturemx_test.go @@ -3,10 +3,10 @@ package cadentaperturemx import ( "testing" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/cadent_aperture_mx/params_test.go b/adapters/cadent_aperture_mx/params_test.go index b5b7355376f..6691537d02c 100644 --- a/adapters/cadent_aperture_mx/params_test.go +++ b/adapters/cadent_aperture_mx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/ccx/ccx.go b/adapters/ccx/ccx.go index 09b89b2c9af..ef5a6a17a9a 100644 --- a/adapters/ccx/ccx.go +++ b/adapters/ccx/ccx.go @@ -6,11 +6,11 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/errortypes" ) type adapter struct { diff --git a/adapters/ccx/ccx_test.go b/adapters/ccx/ccx_test.go index 4d02c9848fd..86f85dc6eb7 100644 --- a/adapters/ccx/ccx_test.go +++ b/adapters/ccx/ccx_test.go @@ -3,9 +3,9 @@ package ccx import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/ccx/params_test.go b/adapters/ccx/params_test.go index ecd9421333b..cb500fec509 100644 --- a/adapters/ccx/params_test.go +++ b/adapters/ccx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/coinzilla/coinzilla.go b/adapters/coinzilla/coinzilla.go index 039f3ecaf25..bc04758e537 100644 --- a/adapters/coinzilla/coinzilla.go +++ b/adapters/coinzilla/coinzilla.go @@ -6,11 +6,11 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/errortypes" ) func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { diff --git a/adapters/coinzilla/coinzilla_test.go b/adapters/coinzilla/coinzilla_test.go index 6c79a2b12d1..39b3bb1f6e5 100644 --- a/adapters/coinzilla/coinzilla_test.go +++ b/adapters/coinzilla/coinzilla_test.go @@ -3,9 +3,9 @@ package coinzilla import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsDir = "coinzillatest" diff --git a/adapters/coinzilla/params_test.go b/adapters/coinzilla/params_test.go index fa24b144769..dd2cd757496 100644 --- a/adapters/coinzilla/params_test.go +++ b/adapters/coinzilla/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/colossus/colossus.go b/adapters/colossus/colossus.go index 65dd5c5dc9d..bc9836984ce 100644 --- a/adapters/colossus/colossus.go +++ b/adapters/colossus/colossus.go @@ -7,10 +7,10 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type ColossusAdapter struct { diff --git a/adapters/colossus/colossus_test.go b/adapters/colossus/colossus_test.go index 3b0f0fc5efe..6298b7f3958 100644 --- a/adapters/colossus/colossus_test.go +++ b/adapters/colossus/colossus_test.go @@ -3,9 +3,9 @@ package colossus import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/colossus/params_test.go b/adapters/colossus/params_test.go index 09fea5981c6..ebafd637d6a 100644 --- a/adapters/colossus/params_test.go +++ b/adapters/colossus/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the colossus schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/compass/compass.go b/adapters/compass/compass.go index 460d5307b45..3edf9b6f157 100644 --- a/adapters/compass/compass.go +++ b/adapters/compass/compass.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/compass/compass_test.go b/adapters/compass/compass_test.go index ad7a4ecf61b..08d9d1bb46e 100644 --- a/adapters/compass/compass_test.go +++ b/adapters/compass/compass_test.go @@ -3,9 +3,9 @@ package compass import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/compass/params_test.go b/adapters/compass/params_test.go index fca398b06fb..37074bb40f7 100644 --- a/adapters/compass/params_test.go +++ b/adapters/compass/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/connectad/connectad.go b/adapters/connectad/connectad.go index 1c33d6bf3c6..ee2e8eeb699 100644 --- a/adapters/connectad/connectad.go +++ b/adapters/connectad/connectad.go @@ -8,10 +8,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type ConnectAdAdapter struct { diff --git a/adapters/connectad/connectad_test.go b/adapters/connectad/connectad_test.go index 037ccbb0a3d..6ce9dd3eade 100644 --- a/adapters/connectad/connectad_test.go +++ b/adapters/connectad/connectad_test.go @@ -3,9 +3,9 @@ package connectad import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/connectad/params_test.go b/adapters/connectad/params_test.go index 6d55b1ce7d9..2ab3c4902e7 100644 --- a/adapters/connectad/params_test.go +++ b/adapters/connectad/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/consumable/consumable.go b/adapters/consumable/consumable.go index 0e16497774c..635bc7058ed 100644 --- a/adapters/consumable/consumable.go +++ b/adapters/consumable/consumable.go @@ -9,11 +9,11 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy/ccpa" ) type ConsumableAdapter struct { diff --git a/adapters/consumable/consumable_test.go b/adapters/consumable/consumable_test.go index 5cfe3fe2824..e3bedce5e2f 100644 --- a/adapters/consumable/consumable_test.go +++ b/adapters/consumable/consumable_test.go @@ -4,10 +4,10 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/consumable/params_test.go b/adapters/consumable/params_test.go index 570b4128339..fdb3edec41b 100644 --- a/adapters/consumable/params_test.go +++ b/adapters/consumable/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/consumable.json diff --git a/adapters/conversant/conversant.go b/adapters/conversant/conversant.go index ec4d2078df8..56ed4b0865c 100644 --- a/adapters/conversant/conversant.go +++ b/adapters/conversant/conversant.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type ConversantAdapter struct { diff --git a/adapters/conversant/conversant_test.go b/adapters/conversant/conversant_test.go index ac3f5a2b633..1c08ea557ed 100644 --- a/adapters/conversant/conversant_test.go +++ b/adapters/conversant/conversant_test.go @@ -3,9 +3,9 @@ package conversant import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/cpmstar/cpmstar.go b/adapters/cpmstar/cpmstar.go index ecb963311b9..f337c1477c5 100644 --- a/adapters/cpmstar/cpmstar.go +++ b/adapters/cpmstar/cpmstar.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type Adapter struct { diff --git a/adapters/cpmstar/cpmstar_test.go b/adapters/cpmstar/cpmstar_test.go index 9218ef4ce2c..2a08258358b 100644 --- a/adapters/cpmstar/cpmstar_test.go +++ b/adapters/cpmstar/cpmstar_test.go @@ -3,9 +3,9 @@ package cpmstar import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/cpmstar/params_test.go b/adapters/cpmstar/params_test.go index 45b1fbefd96..e0dcb9ced52 100644 --- a/adapters/cpmstar/params_test.go +++ b/adapters/cpmstar/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/cpmstar.json diff --git a/adapters/criteo/criteo.go b/adapters/criteo/criteo.go index 11d8d166ba6..cef79d46756 100644 --- a/adapters/criteo/criteo.go +++ b/adapters/criteo/criteo.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/criteo/criteo_test.go b/adapters/criteo/criteo_test.go index 28654acd310..ae538246444 100644 --- a/adapters/criteo/criteo_test.go +++ b/adapters/criteo/criteo_test.go @@ -1,10 +1,11 @@ package criteo import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/criteo/params_test.go b/adapters/criteo/params_test.go index c373de0a83f..aa17aa87e2e 100644 --- a/adapters/criteo/params_test.go +++ b/adapters/criteo/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/criteo.json diff --git a/adapters/cwire/cwire.go b/adapters/cwire/cwire.go index 97a51ed764b..0c51fed908d 100644 --- a/adapters/cwire/cwire.go +++ b/adapters/cwire/cwire.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) /* diff --git a/adapters/cwire/cwire_test.go b/adapters/cwire/cwire_test.go index b51b4e1b007..bd5f32e99ff 100644 --- a/adapters/cwire/cwire_test.go +++ b/adapters/cwire/cwire_test.go @@ -3,9 +3,9 @@ package cwire import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/cwire/params_test.go b/adapters/cwire/params_test.go index 99689977530..d8c8b117069 100644 --- a/adapters/cwire/params_test.go +++ b/adapters/cwire/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/cwire.json diff --git a/adapters/datablocks/datablocks.go b/adapters/datablocks/datablocks.go index a39721a44a3..22bf67bdbbe 100644 --- a/adapters/datablocks/datablocks.go +++ b/adapters/datablocks/datablocks.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type DatablocksAdapter struct { diff --git a/adapters/datablocks/datablocks_test.go b/adapters/datablocks/datablocks_test.go index 3d4d746d357..d97560ce332 100644 --- a/adapters/datablocks/datablocks_test.go +++ b/adapters/datablocks/datablocks_test.go @@ -3,9 +3,9 @@ package datablocks import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/decenterads/decenterads.go b/adapters/decenterads/decenterads.go index d959c9b41ca..bf673ee1691 100644 --- a/adapters/decenterads/decenterads.go +++ b/adapters/decenterads/decenterads.go @@ -8,10 +8,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/decenterads/decenterads_test.go b/adapters/decenterads/decenterads_test.go index 3c1ed971833..dfb5161b9c4 100644 --- a/adapters/decenterads/decenterads_test.go +++ b/adapters/decenterads/decenterads_test.go @@ -3,9 +3,9 @@ package decenterads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/decenterads/params_test.go b/adapters/decenterads/params_test.go index 3d3708be789..ef8b47cce41 100644 --- a/adapters/decenterads/params_test.go +++ b/adapters/decenterads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the decenterads schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/deepintent/deepintent.go b/adapters/deepintent/deepintent.go index 765c43e5c13..1bda503ae32 100644 --- a/adapters/deepintent/deepintent.go +++ b/adapters/deepintent/deepintent.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const displayManager string = "di_prebid" diff --git a/adapters/deepintent/deepintent_test.go b/adapters/deepintent/deepintent_test.go index 97f685d2f7e..9b2c92967b4 100644 --- a/adapters/deepintent/deepintent_test.go +++ b/adapters/deepintent/deepintent_test.go @@ -3,10 +3,10 @@ package deepintent import ( "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" - "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/deepintent/params_test.go b/adapters/deepintent/params_test.go index 8f37e5a9bd6..4cd43b73ebe 100644 --- a/adapters/deepintent/params_test.go +++ b/adapters/deepintent/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the deepintent schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/definemedia/definemedia.go b/adapters/definemedia/definemedia.go index 3e014e3c16d..789902d0485 100644 --- a/adapters/definemedia/definemedia.go +++ b/adapters/definemedia/definemedia.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/definemedia/definemedia_test.go b/adapters/definemedia/definemedia_test.go index 3ed0cb938b8..8a2a860d0c0 100644 --- a/adapters/definemedia/definemedia_test.go +++ b/adapters/definemedia/definemedia_test.go @@ -3,9 +3,9 @@ package definemedia import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/definemedia/params_test.go b/adapters/definemedia/params_test.go index 63ef5272669..45b5be4eae5 100644 --- a/adapters/definemedia/params_test.go +++ b/adapters/definemedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/dianomi/dianomi.go b/adapters/dianomi/dianomi.go index 10605ef24a7..f7b97748c5b 100644 --- a/adapters/dianomi/dianomi.go +++ b/adapters/dianomi/dianomi.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/dianomi/dianomi_test.go b/adapters/dianomi/dianomi_test.go index 95c94a02f14..1baa4b591b3 100644 --- a/adapters/dianomi/dianomi_test.go +++ b/adapters/dianomi/dianomi_test.go @@ -3,9 +3,9 @@ package dianomi import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/dianomi/params_test.go b/adapters/dianomi/params_test.go index 462d6d75edd..43a9bf6f4f5 100644 --- a/adapters/dianomi/params_test.go +++ b/adapters/dianomi/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/dianomi.json diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go index 4f1f23a625c..488c3d46453 100644 --- a/adapters/dmx/dmx.go +++ b/adapters/dmx/dmx.go @@ -10,10 +10,10 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type DmxAdapter struct { diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go index 1634e6b5956..c709d5b1617 100644 --- a/adapters/dmx/dmx_test.go +++ b/adapters/dmx/dmx_test.go @@ -6,11 +6,11 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" - "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" ) func TestFetchParams(t *testing.T) { diff --git a/adapters/dmx/params_test.go b/adapters/dmx/params_test.go index 0e5250b173e..4470fb23057 100644 --- a/adapters/dmx/params_test.go +++ b/adapters/dmx/params_test.go @@ -2,8 +2,9 @@ package dmx import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/dxkulture/dxkulture.go b/adapters/dxkulture/dxkulture.go index 5347c71a303..8155f59a203 100644 --- a/adapters/dxkulture/dxkulture.go +++ b/adapters/dxkulture/dxkulture.go @@ -6,10 +6,10 @@ import ( "net/http" "net/url" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/openrtb/v19/openrtb2" ) diff --git a/adapters/dxkulture/dxkulture_test.go b/adapters/dxkulture/dxkulture_test.go index 9b2da4f27ab..fb14c2b3c6a 100644 --- a/adapters/dxkulture/dxkulture_test.go +++ b/adapters/dxkulture/dxkulture_test.go @@ -3,9 +3,9 @@ package dxkulture import ( "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" - "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/dxkulture/params_test.go b/adapters/dxkulture/params_test.go index 838d092df8f..d1c2f91a7ce 100644 --- a/adapters/dxkulture/params_test.go +++ b/adapters/dxkulture/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/dxkulture.json diff --git a/adapters/e_volution/evolution.go b/adapters/e_volution/evolution.go index af2c680f99c..4beb488f801 100644 --- a/adapters/e_volution/evolution.go +++ b/adapters/e_volution/evolution.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/e_volution/evolution_test.go b/adapters/e_volution/evolution_test.go index 9752a4c7587..4529a742077 100644 --- a/adapters/e_volution/evolution_test.go +++ b/adapters/e_volution/evolution_test.go @@ -3,9 +3,9 @@ package evolution import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/e_volution/params_test.go b/adapters/e_volution/params_test.go index 2d3602fd72b..6049bce7780 100644 --- a/adapters/e_volution/params_test.go +++ b/adapters/e_volution/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/edge226/edge226.go b/adapters/edge226/edge226.go index a124cb47eca..2196006b766 100644 --- a/adapters/edge226/edge226.go +++ b/adapters/edge226/edge226.go @@ -6,9 +6,9 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/edge226/edge226_test.go b/adapters/edge226/edge226_test.go index c5e40ce726d..fea4abff1a9 100644 --- a/adapters/edge226/edge226_test.go +++ b/adapters/edge226/edge226_test.go @@ -3,9 +3,9 @@ package edge226 import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/edge226/params_test.go b/adapters/edge226/params_test.go index 3191bc62136..21a83bd65fd 100644 --- a/adapters/edge226/params_test.go +++ b/adapters/edge226/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/emtv/emtv.go b/adapters/emtv/emtv.go index 5dbd1e1bde7..0f0cae04f1f 100644 --- a/adapters/emtv/emtv.go +++ b/adapters/emtv/emtv.go @@ -7,10 +7,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/emtv/emtv_test.go b/adapters/emtv/emtv_test.go index b1287357681..238e463a0d5 100644 --- a/adapters/emtv/emtv_test.go +++ b/adapters/emtv/emtv_test.go @@ -3,9 +3,9 @@ package emtv import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/emtv/params_test.go b/adapters/emtv/params_test.go index 966dd7dd460..40769b97942 100644 --- a/adapters/emtv/params_test.go +++ b/adapters/emtv/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/eplanning/eplanning.go b/adapters/eplanning/eplanning.go index 8342da63ec2..16c30296b83 100644 --- a/adapters/eplanning/eplanning.go +++ b/adapters/eplanning/eplanning.go @@ -13,10 +13,10 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "strconv" ) diff --git a/adapters/eplanning/eplanning_test.go b/adapters/eplanning/eplanning_test.go index c4a33e54c3d..44ab91413d4 100644 --- a/adapters/eplanning/eplanning_test.go +++ b/adapters/eplanning/eplanning_test.go @@ -3,10 +3,10 @@ package eplanning import ( "testing" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/epom/epom.go b/adapters/epom/epom.go index a4e3040f079..37ef9543979 100644 --- a/adapters/epom/epom.go +++ b/adapters/epom/epom.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/epom/epom_test.go b/adapters/epom/epom_test.go index 6769ff6beb1..4e31c2ab982 100644 --- a/adapters/epom/epom_test.go +++ b/adapters/epom/epom_test.go @@ -3,9 +3,9 @@ package epom import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/flipp/flipp.go b/adapters/flipp/flipp.go index 14f31a91efb..c119ec078cc 100644 --- a/adapters/flipp/flipp.go +++ b/adapters/flipp/flipp.go @@ -10,9 +10,9 @@ import ( "github.com/buger/jsonparser" "github.com/gofrs/uuid" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/flipp/flipp_params.go b/adapters/flipp/flipp_params.go index 0eef61b4b89..11f00560ba4 100644 --- a/adapters/flipp/flipp_params.go +++ b/adapters/flipp/flipp_params.go @@ -1,6 +1,6 @@ package flipp -import "github.com/prebid/prebid-server/openrtb_ext" +import "github.com/prebid/prebid-server/v2/openrtb_ext" type CampaignRequestBodyUser struct { Key *string `json:"key"` diff --git a/adapters/flipp/flipp_test.go b/adapters/flipp/flipp_test.go index 79b79e20769..e9ba0d138fd 100644 --- a/adapters/flipp/flipp_test.go +++ b/adapters/flipp/flipp_test.go @@ -3,9 +3,9 @@ package flipp import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/flipp/params_test.go b/adapters/flipp/params_test.go index 56f2fadbbdd..37f1fda6324 100644 --- a/adapters/flipp/params_test.go +++ b/adapters/flipp/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/freewheelssp/freewheelssp.go b/adapters/freewheelssp/freewheelssp.go index 06f975b3501..1a1738a4ef7 100644 --- a/adapters/freewheelssp/freewheelssp.go +++ b/adapters/freewheelssp/freewheelssp.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/freewheelssp/freewheelssp_test.go b/adapters/freewheelssp/freewheelssp_test.go index 5f06a29c2fd..ea1b5b7c980 100644 --- a/adapters/freewheelssp/freewheelssp_test.go +++ b/adapters/freewheelssp/freewheelssp_test.go @@ -1,10 +1,11 @@ package freewheelssp import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/frvradn/frvradn.go b/adapters/frvradn/frvradn.go index 4088bf9cdf6..6781dbc50cf 100644 --- a/adapters/frvradn/frvradn.go +++ b/adapters/frvradn/frvradn.go @@ -7,10 +7,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/frvradn/frvradn_test.go b/adapters/frvradn/frvradn_test.go index f50f319b04f..89f3dc50b42 100644 --- a/adapters/frvradn/frvradn_test.go +++ b/adapters/frvradn/frvradn_test.go @@ -1,12 +1,13 @@ package frvradn import ( - "github.com/stretchr/testify/assert" "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/frvradn/params_test.go b/adapters/frvradn/params_test.go index 2d4836c1e13..74a51b26fa2 100644 --- a/adapters/frvradn/params_test.go +++ b/adapters/frvradn/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/gamma/gamma.go b/adapters/gamma/gamma.go index 37862424ac7..fac38735507 100644 --- a/adapters/gamma/gamma.go +++ b/adapters/gamma/gamma.go @@ -9,10 +9,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type GammaAdapter struct { diff --git a/adapters/gamma/gamma_test.go b/adapters/gamma/gamma_test.go index 08709364ce9..78cde32984e 100644 --- a/adapters/gamma/gamma_test.go +++ b/adapters/gamma/gamma_test.go @@ -3,9 +3,9 @@ package gamma import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/gamma/params_test.go b/adapters/gamma/params_test.go index c6a149c23bc..7d2d211b054 100644 --- a/adapters/gamma/params_test.go +++ b/adapters/gamma/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/gamma.json diff --git a/adapters/gamoshi/gamoshi.go b/adapters/gamoshi/gamoshi.go index 10c5127e613..db066364bf0 100644 --- a/adapters/gamoshi/gamoshi.go +++ b/adapters/gamoshi/gamoshi.go @@ -7,10 +7,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type GamoshiAdapter struct { diff --git a/adapters/gamoshi/gamoshi_test.go b/adapters/gamoshi/gamoshi_test.go index 979f54cddab..4c787aeb700 100644 --- a/adapters/gamoshi/gamoshi_test.go +++ b/adapters/gamoshi/gamoshi_test.go @@ -3,9 +3,9 @@ package gamoshi import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamplesWithConfiguredURI(t *testing.T) { diff --git a/adapters/gamoshi/params_test.go b/adapters/gamoshi/params_test.go index b9659aaa68a..6fb5d9ee08b 100644 --- a/adapters/gamoshi/params_test.go +++ b/adapters/gamoshi/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/gamoshi.json diff --git a/adapters/globalsun/globalsun.go b/adapters/globalsun/globalsun.go index 6c381fbc0e4..3406f36b704 100644 --- a/adapters/globalsun/globalsun.go +++ b/adapters/globalsun/globalsun.go @@ -7,10 +7,10 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/globalsun/globalsun_test.go b/adapters/globalsun/globalsun_test.go index 0237422dc84..a2f33770486 100644 --- a/adapters/globalsun/globalsun_test.go +++ b/adapters/globalsun/globalsun_test.go @@ -3,9 +3,9 @@ package globalsun import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/globalsun/params_test.go b/adapters/globalsun/params_test.go index af71ad516c2..87d6427ad8b 100644 --- a/adapters/globalsun/params_test.go +++ b/adapters/globalsun/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/gothamads/gothamads.go b/adapters/gothamads/gothamads.go index e486585c958..301a4613677 100644 --- a/adapters/gothamads/gothamads.go +++ b/adapters/gothamads/gothamads.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/gothamads/gothamads_test.go b/adapters/gothamads/gothamads_test.go index 82e34dde1b3..5dde1527f3f 100644 --- a/adapters/gothamads/gothamads_test.go +++ b/adapters/gothamads/gothamads_test.go @@ -3,9 +3,9 @@ package gothamads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/gothamads/params_test.go b/adapters/gothamads/params_test.go index 1290216d28e..1db984321e1 100644 --- a/adapters/gothamads/params_test.go +++ b/adapters/gothamads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/grid/grid.go b/adapters/grid/grid.go index 4f44f90ab11..0d6bdd76d9b 100644 --- a/adapters/grid/grid.go +++ b/adapters/grid/grid.go @@ -8,11 +8,11 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/maputil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/maputil" ) type GridAdapter struct { diff --git a/adapters/grid/grid_test.go b/adapters/grid/grid_test.go index 38e9341cdaf..f35f0c66582 100644 --- a/adapters/grid/grid_test.go +++ b/adapters/grid/grid_test.go @@ -3,9 +3,9 @@ package grid import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go index a500376e72a..3b954bf5837 100644 --- a/adapters/gumgum/gumgum.go +++ b/adapters/gumgum/gumgum.go @@ -8,10 +8,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // GumGumAdapter implements Bidder interface. diff --git a/adapters/gumgum/gumgum_test.go b/adapters/gumgum/gumgum_test.go index fa78eb10e11..621b4a96b04 100644 --- a/adapters/gumgum/gumgum_test.go +++ b/adapters/gumgum/gumgum_test.go @@ -3,9 +3,9 @@ package gumgum import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/gumgum/params_test.go b/adapters/gumgum/params_test.go index a578ce65762..afee017be03 100644 --- a/adapters/gumgum/params_test.go +++ b/adapters/gumgum/params_test.go @@ -2,8 +2,9 @@ package gumgum import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/huaweiads/huaweiads.go b/adapters/huaweiads/huaweiads.go index d156c2f750f..68622bf9f9e 100644 --- a/adapters/huaweiads/huaweiads.go +++ b/adapters/huaweiads/huaweiads.go @@ -19,10 +19,10 @@ import ( nativeRequests "github.com/prebid/openrtb/v19/native1/request" nativeResponse "github.com/prebid/openrtb/v19/native1/response" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const huaweiAdxApiVersion = "3.4" diff --git a/adapters/huaweiads/huaweiads_test.go b/adapters/huaweiads/huaweiads_test.go index f523f0b9d94..9e7d0b55364 100644 --- a/adapters/huaweiads/huaweiads_test.go +++ b/adapters/huaweiads/huaweiads_test.go @@ -3,9 +3,9 @@ package huaweiads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/huaweiads/params_test.go b/adapters/huaweiads/params_test.go index 7347725b299..c93c3c3ac55 100644 --- a/adapters/huaweiads/params_test.go +++ b/adapters/huaweiads/params_test.go @@ -2,8 +2,9 @@ package huaweiads import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/imds/imds.go b/adapters/imds/imds.go index 02d30828966..c5ef45176ab 100644 --- a/adapters/imds/imds.go +++ b/adapters/imds/imds.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const adapterVersion string = "pbs-go/1.0.0" diff --git a/adapters/imds/imds_test.go b/adapters/imds/imds_test.go index 5fab0509c1d..6e8165fbe6a 100644 --- a/adapters/imds/imds_test.go +++ b/adapters/imds/imds_test.go @@ -3,9 +3,9 @@ package imds import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/imds/params_test.go b/adapters/imds/params_test.go index e2052b4c262..740105c3183 100644 --- a/adapters/imds/params_test.go +++ b/adapters/imds/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/imds.json diff --git a/adapters/impactify/impactify.go b/adapters/impactify/impactify.go index bfbcdfbf8a2..73dc9450a3b 100644 --- a/adapters/impactify/impactify.go +++ b/adapters/impactify/impactify.go @@ -8,10 +8,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/impactify/impactify_test.go b/adapters/impactify/impactify_test.go index 550ea7ad498..b9518d5e2a3 100644 --- a/adapters/impactify/impactify_test.go +++ b/adapters/impactify/impactify_test.go @@ -3,9 +3,9 @@ package impactify import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/impactify/params_test.go b/adapters/impactify/params_test.go index 845ea29f00b..2fa8b24d627 100644 --- a/adapters/impactify/params_test.go +++ b/adapters/impactify/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/improvedigital/improvedigital.go b/adapters/improvedigital/improvedigital.go index c26a200a486..2cad15fa033 100644 --- a/adapters/improvedigital/improvedigital.go +++ b/adapters/improvedigital/improvedigital.go @@ -3,15 +3,16 @@ package improvedigital import ( "encoding/json" "fmt" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" "net/http" "regexp" "strconv" "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/improvedigital/improvedigital_test.go b/adapters/improvedigital/improvedigital_test.go index 15ca5d5f033..b89e03320fa 100644 --- a/adapters/improvedigital/improvedigital_test.go +++ b/adapters/improvedigital/improvedigital_test.go @@ -3,9 +3,9 @@ package improvedigital import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/improvedigital/params_test.go b/adapters/improvedigital/params_test.go index 13bdd807560..3767c0316a5 100644 --- a/adapters/improvedigital/params_test.go +++ b/adapters/improvedigital/params_test.go @@ -2,8 +2,9 @@ package improvedigital import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/infoawarebidder.go b/adapters/infoawarebidder.go index 538a46ae05c..3131a6404b1 100644 --- a/adapters/infoawarebidder.go +++ b/adapters/infoawarebidder.go @@ -4,9 +4,9 @@ import ( "fmt" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // InfoAwareBidder wraps a Bidder to ensure all requests abide by the capabilities and diff --git a/adapters/infoawarebidder_test.go b/adapters/infoawarebidder_test.go index 87d9d4d442a..38570e262d4 100644 --- a/adapters/infoawarebidder_test.go +++ b/adapters/infoawarebidder_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/adapters/infytv/infytv.go b/adapters/infytv/infytv.go index b403e710911..7e93a7296a5 100644 --- a/adapters/infytv/infytv.go +++ b/adapters/infytv/infytv.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/infytv/infytv_test.go b/adapters/infytv/infytv_test.go index 6834e99fa36..80527bbc4ae 100644 --- a/adapters/infytv/infytv_test.go +++ b/adapters/infytv/infytv_test.go @@ -3,9 +3,9 @@ package infytv import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/infytv/params_test.go b/adapters/infytv/params_test.go index 6719b102622..53230cbf263 100644 --- a/adapters/infytv/params_test.go +++ b/adapters/infytv/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go index a5773c83be9..54d644cdc7d 100644 --- a/adapters/inmobi/inmobi.go +++ b/adapters/inmobi/inmobi.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type InMobiAdapter struct { diff --git a/adapters/inmobi/inmobi_test.go b/adapters/inmobi/inmobi_test.go index 0b6f5ffbd0e..40e77f5fbc3 100644 --- a/adapters/inmobi/inmobi_test.go +++ b/adapters/inmobi/inmobi_test.go @@ -3,9 +3,9 @@ package inmobi import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/interactiveoffers/interactiveoffers.go b/adapters/interactiveoffers/interactiveoffers.go index 2f90b707244..2e1a6417f14 100644 --- a/adapters/interactiveoffers/interactiveoffers.go +++ b/adapters/interactiveoffers/interactiveoffers.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/interactiveoffers/interactiveoffers_test.go b/adapters/interactiveoffers/interactiveoffers_test.go index 7805ae665bb..daf83869a95 100644 --- a/adapters/interactiveoffers/interactiveoffers_test.go +++ b/adapters/interactiveoffers/interactiveoffers_test.go @@ -3,9 +3,9 @@ package interactiveoffers import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/interactiveoffers/params_test.go b/adapters/interactiveoffers/params_test.go index 193c1b84839..8f14a51b512 100644 --- a/adapters/interactiveoffers/params_test.go +++ b/adapters/interactiveoffers/params_test.go @@ -2,8 +2,9 @@ package interactiveoffers import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/invibes/invibes.go b/adapters/invibes/invibes.go index b1272415d93..e1b978831b8 100644 --- a/adapters/invibes/invibes.go +++ b/adapters/invibes/invibes.go @@ -10,12 +10,12 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const adapterVersion = "prebid_1.0.0" diff --git a/adapters/invibes/invibes_test.go b/adapters/invibes/invibes_test.go index 1ade5d276cd..e09837764f9 100644 --- a/adapters/invibes/invibes_test.go +++ b/adapters/invibes/invibes_test.go @@ -3,9 +3,9 @@ package invibes import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/invibes/params_test.go b/adapters/invibes/params_test.go index 8c3a26b4eac..88e08d1dc80 100644 --- a/adapters/invibes/params_test.go +++ b/adapters/invibes/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/iqx/iqx.go b/adapters/iqx/iqx.go index 543d988bce5..edfefa1895e 100644 --- a/adapters/iqx/iqx.go +++ b/adapters/iqx/iqx.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type bidType struct { diff --git a/adapters/iqx/iqxtest_test.go b/adapters/iqx/iqxtest_test.go index 18eaf5fa5ba..7385d37af91 100644 --- a/adapters/iqx/iqxtest_test.go +++ b/adapters/iqx/iqxtest_test.go @@ -3,9 +3,9 @@ package iqx import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/iqx/params_test.go b/adapters/iqx/params_test.go index 94bfb1d0926..23c4d007acb 100644 --- a/adapters/iqx/params_test.go +++ b/adapters/iqx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/iqzone/iqzone.go b/adapters/iqzone/iqzone.go index 9c36a021e21..ce095c97009 100644 --- a/adapters/iqzone/iqzone.go +++ b/adapters/iqzone/iqzone.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/iqzone/iqzone_test.go b/adapters/iqzone/iqzone_test.go index 3e1afbe1909..c5713953f40 100644 --- a/adapters/iqzone/iqzone_test.go +++ b/adapters/iqzone/iqzone_test.go @@ -3,9 +3,9 @@ package iqzone import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/iqzone/params_test.go b/adapters/iqzone/params_test.go index c06f5e7fab4..2c6bb223845 100644 --- a/adapters/iqzone/params_test.go +++ b/adapters/iqzone/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index 300858e3205..f000cbc1625 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -7,11 +7,11 @@ import ( "sort" "strings" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/version" "github.com/prebid/openrtb/v19/native1" native1response "github.com/prebid/openrtb/v19/native1/response" diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go index a64e3d0c661..e2651ca523e 100644 --- a/adapters/ix/ix_test.go +++ b/adapters/ix/ix_test.go @@ -4,11 +4,11 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/version" "github.com/stretchr/testify/assert" "github.com/prebid/openrtb/v19/adcom1" diff --git a/adapters/ix/params_test.go b/adapters/ix/params_test.go index 8ba937c12f4..ba426c0b27b 100644 --- a/adapters/ix/params_test.go +++ b/adapters/ix/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/jixie/jixie.go b/adapters/jixie/jixie.go index 6018b73709b..fa87bd5da03 100644 --- a/adapters/jixie/jixie.go +++ b/adapters/jixie/jixie.go @@ -7,10 +7,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/jixie/jixie_test.go b/adapters/jixie/jixie_test.go index 56260a6c04d..dff2b3574d3 100644 --- a/adapters/jixie/jixie_test.go +++ b/adapters/jixie/jixie_test.go @@ -3,9 +3,9 @@ package jixie import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/jixie/params_test.go b/adapters/jixie/params_test.go index 4bc2e3080c1..cbcdbe959de 100644 --- a/adapters/jixie/params_test.go +++ b/adapters/jixie/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/kargo/kargo.go b/adapters/kargo/kargo.go index e10bbbe466b..003275c51d3 100644 --- a/adapters/kargo/kargo.go +++ b/adapters/kargo/kargo.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/kargo/kargo_test.go b/adapters/kargo/kargo_test.go index 482ef2361cf..f2ceb46f643 100644 --- a/adapters/kargo/kargo_test.go +++ b/adapters/kargo/kargo_test.go @@ -3,9 +3,9 @@ package kargo import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/kargo/params_test.go b/adapters/kargo/params_test.go index cb9ffaf1028..9907d67116c 100644 --- a/adapters/kargo/params_test.go +++ b/adapters/kargo/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/kayzen/kayzen.go b/adapters/kayzen/kayzen.go index 3dac0a50e86..a4c459ac2a2 100644 --- a/adapters/kayzen/kayzen.go +++ b/adapters/kayzen/kayzen.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/kayzen/kayzen_test.go b/adapters/kayzen/kayzen_test.go index dc6a7db4735..a2cc98a7798 100644 --- a/adapters/kayzen/kayzen_test.go +++ b/adapters/kayzen/kayzen_test.go @@ -3,9 +3,9 @@ package kayzen import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/kayzen/params_test.go b/adapters/kayzen/params_test.go index 07bd0851a97..bd03e23df5b 100644 --- a/adapters/kayzen/params_test.go +++ b/adapters/kayzen/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/kidoz/kidoz.go b/adapters/kidoz/kidoz.go index b2637768bb1..f18ed98c07b 100644 --- a/adapters/kidoz/kidoz.go +++ b/adapters/kidoz/kidoz.go @@ -7,10 +7,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type KidozAdapter struct { diff --git a/adapters/kidoz/kidoz_test.go b/adapters/kidoz/kidoz_test.go index ffde298ee0e..0a5e21fd62d 100644 --- a/adapters/kidoz/kidoz_test.go +++ b/adapters/kidoz/kidoz_test.go @@ -6,10 +6,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/kidoz/params_test.go b/adapters/kidoz/params_test.go index 073d7382d68..fad4e681288 100644 --- a/adapters/kidoz/params_test.go +++ b/adapters/kidoz/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/kiviads/kiviads.go b/adapters/kiviads/kiviads.go index 88d245a5db2..8bdf72145d4 100644 --- a/adapters/kiviads/kiviads.go +++ b/adapters/kiviads/kiviads.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/kiviads/kiviads_test.go b/adapters/kiviads/kiviads_test.go index 41e9bcbcdee..a6725234e6a 100644 --- a/adapters/kiviads/kiviads_test.go +++ b/adapters/kiviads/kiviads_test.go @@ -3,9 +3,9 @@ package kiviads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/kiviads/params_test.go b/adapters/kiviads/params_test.go index 177e2f149b3..09ee3e24265 100644 --- a/adapters/kiviads/params_test.go +++ b/adapters/kiviads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/krushmedia/krushmedia.go b/adapters/krushmedia/krushmedia.go index 71a869a42a1..e809960db8c 100644 --- a/adapters/krushmedia/krushmedia.go +++ b/adapters/krushmedia/krushmedia.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type KrushmediaAdapter struct { diff --git a/adapters/krushmedia/krushmedia_test.go b/adapters/krushmedia/krushmedia_test.go index 7ffbc0a9361..e7d71422dfe 100644 --- a/adapters/krushmedia/krushmedia_test.go +++ b/adapters/krushmedia/krushmedia_test.go @@ -3,9 +3,9 @@ package krushmedia import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/krushmedia/params_test.go b/adapters/krushmedia/params_test.go index 26daa56e10b..4129fa1bd29 100644 --- a/adapters/krushmedia/params_test.go +++ b/adapters/krushmedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/lemmadigital/lemmadigital.go b/adapters/lemmadigital/lemmadigital.go index 3b77de52984..d09793fa4e8 100644 --- a/adapters/lemmadigital/lemmadigital.go +++ b/adapters/lemmadigital/lemmadigital.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/lemmadigital/lemmadigital_test.go b/adapters/lemmadigital/lemmadigital_test.go index 0372fa81d6b..e0062c0b565 100644 --- a/adapters/lemmadigital/lemmadigital_test.go +++ b/adapters/lemmadigital/lemmadigital_test.go @@ -3,9 +3,9 @@ package lemmadigital import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/lemmadigital/params_test.go b/adapters/lemmadigital/params_test.go index 57bc7d83e79..4ba61319b33 100644 --- a/adapters/lemmadigital/params_test.go +++ b/adapters/lemmadigital/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Tests for static/bidder-params/lemmadigital.json diff --git a/adapters/liftoff/liftoff.go b/adapters/liftoff/liftoff.go index b649da6f631..538e6aa1cda 100644 --- a/adapters/liftoff/liftoff.go +++ b/adapters/liftoff/liftoff.go @@ -8,9 +8,9 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const SupportedCurrency = "USD" diff --git a/adapters/liftoff/liftoff_test.go b/adapters/liftoff/liftoff_test.go index 0cb717dcc5a..4003bd21171 100644 --- a/adapters/liftoff/liftoff_test.go +++ b/adapters/liftoff/liftoff_test.go @@ -3,9 +3,9 @@ package liftoff import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/liftoff/param_test.go b/adapters/liftoff/param_test.go index d7cd5d73c09..a5e5a61fb9a 100644 --- a/adapters/liftoff/param_test.go +++ b/adapters/liftoff/param_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/limelightDigital/limelightDigital.go b/adapters/limelightDigital/limelightDigital.go index 86d8df94233..d9920c72383 100644 --- a/adapters/limelightDigital/limelightDigital.go +++ b/adapters/limelightDigital/limelightDigital.go @@ -10,11 +10,11 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/limelightDigital/limelightDigital_test.go b/adapters/limelightDigital/limelightDigital_test.go index 0c4105b59c9..beb4222f5c8 100644 --- a/adapters/limelightDigital/limelightDigital_test.go +++ b/adapters/limelightDigital/limelightDigital_test.go @@ -1,12 +1,13 @@ package limelightDigital import ( - "github.com/stretchr/testify/assert" "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/limelightDigital/params_test.go b/adapters/limelightDigital/params_test.go index 4ccef77c338..9a61e9ba7c9 100644 --- a/adapters/limelightDigital/params_test.go +++ b/adapters/limelightDigital/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/lm_kiviads/lmkiviads.go b/adapters/lm_kiviads/lmkiviads.go index c121be3b24b..03c17e6439c 100644 --- a/adapters/lm_kiviads/lmkiviads.go +++ b/adapters/lm_kiviads/lmkiviads.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type bidType struct { diff --git a/adapters/lm_kiviads/lmkiviads_test.go b/adapters/lm_kiviads/lmkiviads_test.go index dfc8a5db0c4..01bf47ef8a7 100644 --- a/adapters/lm_kiviads/lmkiviads_test.go +++ b/adapters/lm_kiviads/lmkiviads_test.go @@ -3,9 +3,9 @@ package lmkiviads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/lm_kiviads/params_test.go b/adapters/lm_kiviads/params_test.go index f40ad516684..1f3361b2581 100644 --- a/adapters/lm_kiviads/params_test.go +++ b/adapters/lm_kiviads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/lockerdome/lockerdome.go b/adapters/lockerdome/lockerdome.go index 7aa46080c73..c52a51a1003 100644 --- a/adapters/lockerdome/lockerdome.go +++ b/adapters/lockerdome/lockerdome.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const unexpectedStatusCodeMessage = "Unexpected status code: %d. Run with request.debug = 1 for more info" diff --git a/adapters/lockerdome/lockerdome_test.go b/adapters/lockerdome/lockerdome_test.go index 1f807044b27..892fce2183a 100644 --- a/adapters/lockerdome/lockerdome_test.go +++ b/adapters/lockerdome/lockerdome_test.go @@ -3,9 +3,9 @@ package lockerdome import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/lockerdome/params_test.go b/adapters/lockerdome/params_test.go index c2c9185e374..9b90ec0b888 100644 --- a/adapters/lockerdome/params_test.go +++ b/adapters/lockerdome/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file tests static/bidder-params/lockerdome.json diff --git a/adapters/logan/logan.go b/adapters/logan/logan.go index db0888f8905..e5e76bca75b 100644 --- a/adapters/logan/logan.go +++ b/adapters/logan/logan.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/logan/logan_test.go b/adapters/logan/logan_test.go index da3a8741cff..219fbb50fe0 100644 --- a/adapters/logan/logan_test.go +++ b/adapters/logan/logan_test.go @@ -3,9 +3,9 @@ package logan import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/logan/params_test.go b/adapters/logan/params_test.go index 48da1912e61..55633b47c36 100644 --- a/adapters/logan/params_test.go +++ b/adapters/logan/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/logicad/logicad.go b/adapters/logicad/logicad.go index ffbb783bafb..2bd60077ae0 100644 --- a/adapters/logicad/logicad.go +++ b/adapters/logicad/logicad.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type LogicadAdapter struct { diff --git a/adapters/logicad/logicad_test.go b/adapters/logicad/logicad_test.go index 98295cc4a28..551bd133a9c 100644 --- a/adapters/logicad/logicad_test.go +++ b/adapters/logicad/logicad_test.go @@ -3,9 +3,9 @@ package logicad import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/logicad/params_test.go b/adapters/logicad/params_test.go index eb34452811b..c45297d15c2 100644 --- a/adapters/logicad/params_test.go +++ b/adapters/logicad/params_test.go @@ -2,8 +2,9 @@ package logicad import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/lunamedia/lunamedia.go b/adapters/lunamedia/lunamedia.go index 24717e6ba52..99a54deb82f 100644 --- a/adapters/lunamedia/lunamedia.go +++ b/adapters/lunamedia/lunamedia.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type LunaMediaAdapter struct { diff --git a/adapters/lunamedia/lunamedia_test.go b/adapters/lunamedia/lunamedia_test.go index 961cd545303..c950ce4d25f 100644 --- a/adapters/lunamedia/lunamedia_test.go +++ b/adapters/lunamedia/lunamedia_test.go @@ -3,9 +3,9 @@ package lunamedia import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/lunamedia/params_test.go b/adapters/lunamedia/params_test.go index b4faeea1f77..12ecc65f420 100644 --- a/adapters/lunamedia/params_test.go +++ b/adapters/lunamedia/params_test.go @@ -2,8 +2,9 @@ package lunamedia import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/mabidder/mabidder.go b/adapters/mabidder/mabidder.go index 184f068b333..9f7323c005d 100644 --- a/adapters/mabidder/mabidder.go +++ b/adapters/mabidder/mabidder.go @@ -4,9 +4,9 @@ import ( "encoding/json" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type serverResponse struct { diff --git a/adapters/mabidder/mabidder_test.go b/adapters/mabidder/mabidder_test.go index 89cfd31633e..31c28788ad2 100644 --- a/adapters/mabidder/mabidder_test.go +++ b/adapters/mabidder/mabidder_test.go @@ -3,9 +3,9 @@ package mabidder import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/mabidder/params_test.go b/adapters/mabidder/params_test.go index b0f3762d843..878e278438d 100644 --- a/adapters/mabidder/params_test.go +++ b/adapters/mabidder/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/madvertise/madvertise.go b/adapters/madvertise/madvertise.go index 32b84c6e65a..41097f62ab6 100644 --- a/adapters/madvertise/madvertise.go +++ b/adapters/madvertise/madvertise.go @@ -8,11 +8,11 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/madvertise/madvertise_test.go b/adapters/madvertise/madvertise_test.go index 1d48b6dab3c..3d412b1e154 100644 --- a/adapters/madvertise/madvertise_test.go +++ b/adapters/madvertise/madvertise_test.go @@ -3,9 +3,9 @@ package madvertise import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/madvertise/params_test.go b/adapters/madvertise/params_test.go index 4b73d57d43e..55454399155 100644 --- a/adapters/madvertise/params_test.go +++ b/adapters/madvertise/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/Madvertise.json diff --git a/adapters/marsmedia/marsmedia.go b/adapters/marsmedia/marsmedia.go index 6bba910aee0..c75b4f7ad6b 100644 --- a/adapters/marsmedia/marsmedia.go +++ b/adapters/marsmedia/marsmedia.go @@ -7,10 +7,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type MarsmediaAdapter struct { diff --git a/adapters/marsmedia/marsmedia_test.go b/adapters/marsmedia/marsmedia_test.go index 8fe01a72f73..6c75ef76c37 100644 --- a/adapters/marsmedia/marsmedia_test.go +++ b/adapters/marsmedia/marsmedia_test.go @@ -3,9 +3,9 @@ package marsmedia import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/marsmedia/params_test.go b/adapters/marsmedia/params_test.go index f78ad1c3dc4..ee79015b05c 100644 --- a/adapters/marsmedia/params_test.go +++ b/adapters/marsmedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/marsmedia.json diff --git a/adapters/medianet/medianet.go b/adapters/medianet/medianet.go index a5bf8e60df4..f84705aaa28 100644 --- a/adapters/medianet/medianet.go +++ b/adapters/medianet/medianet.go @@ -7,10 +7,10 @@ import ( "net/url" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/medianet/medianet_test.go b/adapters/medianet/medianet_test.go index 6403b3f2eb0..097e8e48727 100644 --- a/adapters/medianet/medianet_test.go +++ b/adapters/medianet/medianet_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/medianet/params_test.go b/adapters/medianet/params_test.go index a1e5e834ba6..7c999a66428 100644 --- a/adapters/medianet/params_test.go +++ b/adapters/medianet/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/medianet.json diff --git a/adapters/mgid/mgid.go b/adapters/mgid/mgid.go index 99d1946d3f1..712c6cd8928 100644 --- a/adapters/mgid/mgid.go +++ b/adapters/mgid/mgid.go @@ -7,10 +7,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type MgidAdapter struct { diff --git a/adapters/mgid/mgid_test.go b/adapters/mgid/mgid_test.go index fba3d8b09c3..21311f5477d 100644 --- a/adapters/mgid/mgid_test.go +++ b/adapters/mgid/mgid_test.go @@ -3,9 +3,9 @@ package mgid import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/mgidX/mgidX.go b/adapters/mgidX/mgidX.go index 29d2df4617c..084fdaebca9 100644 --- a/adapters/mgidX/mgidX.go +++ b/adapters/mgidX/mgidX.go @@ -7,9 +7,9 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/mgidX/mgidX_test.go b/adapters/mgidX/mgidX_test.go index 5a71750788a..7ff4eb93627 100644 --- a/adapters/mgidX/mgidX_test.go +++ b/adapters/mgidX/mgidX_test.go @@ -3,9 +3,9 @@ package mgidX import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/mgidX/params_test.go b/adapters/mgidX/params_test.go index 85779ec6e8c..b3d80207811 100644 --- a/adapters/mgidX/params_test.go +++ b/adapters/mgidX/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/mobfoxpb/mobfoxpb.go b/adapters/mobfoxpb/mobfoxpb.go index 6684d0c3fc2..24e13e26584 100644 --- a/adapters/mobfoxpb/mobfoxpb.go +++ b/adapters/mobfoxpb/mobfoxpb.go @@ -8,10 +8,10 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/mobfoxpb/mobfoxpb_test.go b/adapters/mobfoxpb/mobfoxpb_test.go index 401396adc8d..b3aa4ae4b2c 100644 --- a/adapters/mobfoxpb/mobfoxpb_test.go +++ b/adapters/mobfoxpb/mobfoxpb_test.go @@ -3,9 +3,9 @@ package mobfoxpb import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/mobfoxpb/params_test.go b/adapters/mobfoxpb/params_test.go index 799fdcfa61b..ea785163609 100644 --- a/adapters/mobfoxpb/params_test.go +++ b/adapters/mobfoxpb/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the mobfoxpb schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/mobilefuse/mobilefuse.go b/adapters/mobilefuse/mobilefuse.go index c13dbe8a6ae..24461ade471 100644 --- a/adapters/mobilefuse/mobilefuse.go +++ b/adapters/mobilefuse/mobilefuse.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type MobileFuseAdapter struct { diff --git a/adapters/mobilefuse/mobilefuse_test.go b/adapters/mobilefuse/mobilefuse_test.go index 09d46faff66..e1a3a018bb9 100644 --- a/adapters/mobilefuse/mobilefuse_test.go +++ b/adapters/mobilefuse/mobilefuse_test.go @@ -3,9 +3,9 @@ package mobilefuse import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/mobilefuse/params_test.go b/adapters/mobilefuse/params_test.go index dbfd8894e70..8599af5ece0 100644 --- a/adapters/mobilefuse/params_test.go +++ b/adapters/mobilefuse/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(test *testing.T) { diff --git a/adapters/motorik/motorik.go b/adapters/motorik/motorik.go index c804c0951fc..95bb4837c9a 100644 --- a/adapters/motorik/motorik.go +++ b/adapters/motorik/motorik.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/motorik/motorik_test.go b/adapters/motorik/motorik_test.go index 1a14fedac7b..5bdd102ddd3 100644 --- a/adapters/motorik/motorik_test.go +++ b/adapters/motorik/motorik_test.go @@ -3,9 +3,9 @@ package motorik import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/motorik/params_test.go b/adapters/motorik/params_test.go index 9ee05b58e07..2cfdf0f965b 100644 --- a/adapters/motorik/params_test.go +++ b/adapters/motorik/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/nextmillennium/nextmillennium.go b/adapters/nextmillennium/nextmillennium.go index f24bb6f7df8..5d10cac2d9e 100644 --- a/adapters/nextmillennium/nextmillennium.go +++ b/adapters/nextmillennium/nextmillennium.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/nextmillennium/nextmillennium_test.go b/adapters/nextmillennium/nextmillennium_test.go index bd75596691f..ec813ba2412 100644 --- a/adapters/nextmillennium/nextmillennium_test.go +++ b/adapters/nextmillennium/nextmillennium_test.go @@ -3,9 +3,9 @@ package nextmillennium import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/nextmillennium/params_test.go b/adapters/nextmillennium/params_test.go index 50a9c377f70..d8ae93d2c5a 100644 --- a/adapters/nextmillennium/params_test.go +++ b/adapters/nextmillennium/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/nobid/nobid.go b/adapters/nobid/nobid.go index 6ea76e1283e..63d20d87fc0 100644 --- a/adapters/nobid/nobid.go +++ b/adapters/nobid/nobid.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // NoBidAdapter - NoBid Adapter definition diff --git a/adapters/nobid/nobid_test.go b/adapters/nobid/nobid_test.go index a8775b74d18..8b48a303053 100644 --- a/adapters/nobid/nobid_test.go +++ b/adapters/nobid/nobid_test.go @@ -3,9 +3,9 @@ package nobid import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/nobid/params_test.go b/adapters/nobid/params_test.go index 75d69943d35..395230df797 100644 --- a/adapters/nobid/params_test.go +++ b/adapters/nobid/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/onetag/onetag.go b/adapters/onetag/onetag.go index 6e642ef1979..9036de80ced 100644 --- a/adapters/onetag/onetag.go +++ b/adapters/onetag/onetag.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/onetag/onetag_test.go b/adapters/onetag/onetag_test.go index 5550f076a99..7a1e539ca29 100644 --- a/adapters/onetag/onetag_test.go +++ b/adapters/onetag/onetag_test.go @@ -3,9 +3,9 @@ package onetag import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/onetag/params_test.go b/adapters/onetag/params_test.go index 4c7326ac9f0..bfc7c6ac27a 100644 --- a/adapters/onetag/params_test.go +++ b/adapters/onetag/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/openweb/openweb.go b/adapters/openweb/openweb.go index 8ef4007f959..45fd2853aec 100644 --- a/adapters/openweb/openweb.go +++ b/adapters/openweb/openweb.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/openweb/openweb_test.go b/adapters/openweb/openweb_test.go index 6332c409623..a63dafd06c2 100644 --- a/adapters/openweb/openweb_test.go +++ b/adapters/openweb/openweb_test.go @@ -3,9 +3,9 @@ package openweb import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/openweb/params_test.go b/adapters/openweb/params_test.go index df614df715f..7a53124fa30 100644 --- a/adapters/openweb/params_test.go +++ b/adapters/openweb/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/openweb.json diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go index bd5e5555a40..f86b7b143fb 100644 --- a/adapters/openx/openx.go +++ b/adapters/openx/openx.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const hbconfig = "hb_pbs_1.0.0" diff --git a/adapters/openx/openx_test.go b/adapters/openx/openx_test.go index c25bd02e52c..d998148da87 100644 --- a/adapters/openx/openx_test.go +++ b/adapters/openx/openx_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/openx/params_test.go b/adapters/openx/params_test.go index 94775b57cb0..4e081dba7e5 100644 --- a/adapters/openx/params_test.go +++ b/adapters/openx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/openx.json diff --git a/adapters/operaads/operaads.go b/adapters/operaads/operaads.go index 4fad9fe0894..f6af4039fb6 100644 --- a/adapters/operaads/operaads.go +++ b/adapters/operaads/operaads.go @@ -8,11 +8,11 @@ import ( "strings" "text/template" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/openrtb/v19/openrtb2" ) diff --git a/adapters/operaads/operaads_test.go b/adapters/operaads/operaads_test.go index fca277fa937..4e51c8393aa 100644 --- a/adapters/operaads/operaads_test.go +++ b/adapters/operaads/operaads_test.go @@ -3,9 +3,9 @@ package operaads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/operaads/params_test.go b/adapters/operaads/params_test.go index 57a60ce9c53..1da80aa8ce3 100644 --- a/adapters/operaads/params_test.go +++ b/adapters/operaads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/operaads.json diff --git a/adapters/orbidder/orbidder.go b/adapters/orbidder/orbidder.go index b4f1a16c114..8f1c582afe7 100644 --- a/adapters/orbidder/orbidder.go +++ b/adapters/orbidder/orbidder.go @@ -8,10 +8,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type OrbidderAdapter struct { diff --git a/adapters/orbidder/orbidder_test.go b/adapters/orbidder/orbidder_test.go index 5a80514ccae..39919da06e0 100644 --- a/adapters/orbidder/orbidder_test.go +++ b/adapters/orbidder/orbidder_test.go @@ -8,10 +8,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/mock" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/orbidder/params_test.go b/adapters/orbidder/params_test.go index 2e130f7a9bd..cd95e222aee 100644 --- a/adapters/orbidder/params_test.go +++ b/adapters/orbidder/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/orbidder.json diff --git a/adapters/outbrain/outbrain.go b/adapters/outbrain/outbrain.go index a7d727a57c6..1d07c082507 100644 --- a/adapters/outbrain/outbrain.go +++ b/adapters/outbrain/outbrain.go @@ -8,10 +8,10 @@ import ( "github.com/prebid/openrtb/v19/native1" nativeResponse "github.com/prebid/openrtb/v19/native1/response" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/outbrain/outbrain_test.go b/adapters/outbrain/outbrain_test.go index 9e667dae83a..4a42679f660 100644 --- a/adapters/outbrain/outbrain_test.go +++ b/adapters/outbrain/outbrain_test.go @@ -3,9 +3,9 @@ package outbrain import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/outbrain/params_test.go b/adapters/outbrain/params_test.go index a8d81d6234d..666724cd6eb 100644 --- a/adapters/outbrain/params_test.go +++ b/adapters/outbrain/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/ownadx/ownadx.go b/adapters/ownadx/ownadx.go index c19343ba47f..77baa63b4cd 100644 --- a/adapters/ownadx/ownadx.go +++ b/adapters/ownadx/ownadx.go @@ -3,14 +3,15 @@ package ownadx import ( "encoding/json" "fmt" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" "net/http" "text/template" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/ownadx/ownadx_test.go b/adapters/ownadx/ownadx_test.go index 9bd7ba70353..07dc928b9b0 100644 --- a/adapters/ownadx/ownadx_test.go +++ b/adapters/ownadx/ownadx_test.go @@ -1,11 +1,12 @@ package ownadx import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/pangle/pangle.go b/adapters/pangle/pangle.go index fcdc0264935..7c18a840025 100644 --- a/adapters/pangle/pangle.go +++ b/adapters/pangle/pangle.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/pangle/pangle_test.go b/adapters/pangle/pangle_test.go index 3653a60c81c..243bbadc90a 100644 --- a/adapters/pangle/pangle_test.go +++ b/adapters/pangle/pangle_test.go @@ -3,9 +3,9 @@ package pangle import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/pangle/param_test.go b/adapters/pangle/param_test.go index 5e1d30b3c7b..e25b7d740c4 100644 --- a/adapters/pangle/param_test.go +++ b/adapters/pangle/param_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/pgamssp/params_test.go b/adapters/pgamssp/params_test.go index 3e603237535..d2f06cbcfa8 100644 --- a/adapters/pgamssp/params_test.go +++ b/adapters/pgamssp/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/pgamssp/pgamssp.go b/adapters/pgamssp/pgamssp.go index 378b8e5cbf6..3e315fedc95 100644 --- a/adapters/pgamssp/pgamssp.go +++ b/adapters/pgamssp/pgamssp.go @@ -6,9 +6,9 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/pgamssp/pgamssp_test.go b/adapters/pgamssp/pgamssp_test.go index 596b813c888..10e72f5d093 100644 --- a/adapters/pgamssp/pgamssp_test.go +++ b/adapters/pgamssp/pgamssp_test.go @@ -3,9 +3,9 @@ package pgamssp import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/pubmatic/params_test.go b/adapters/pubmatic/params_test.go index a5a7773f7af..d5d1d46842f 100644 --- a/adapters/pubmatic/params_test.go +++ b/adapters/pubmatic/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/pubmatic.json diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index bd406d68631..17f43ea0f0a 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -11,10 +11,10 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const MAX_IMPRESSIONS_PUBMATIC = 30 diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 7553519f990..b40bf2d4fb2 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -8,10 +8,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/pubnative/pubnative.go b/adapters/pubnative/pubnative.go index 35265c8a4c3..da3cab34dae 100644 --- a/adapters/pubnative/pubnative.go +++ b/adapters/pubnative/pubnative.go @@ -8,10 +8,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type PubnativeAdapter struct { diff --git a/adapters/pubnative/pubnative_test.go b/adapters/pubnative/pubnative_test.go index 338575a51b0..b1b1bf85e05 100644 --- a/adapters/pubnative/pubnative_test.go +++ b/adapters/pubnative/pubnative_test.go @@ -3,9 +3,9 @@ package pubnative import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/pulsepoint/params_test.go b/adapters/pulsepoint/params_test.go index ac2b314b96f..4b3c6c017db 100644 --- a/adapters/pulsepoint/params_test.go +++ b/adapters/pulsepoint/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index 6c17a6c30f4..f65e010341e 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -6,10 +6,10 @@ import ( "net/http" "strconv" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/openrtb/v19/openrtb2" ) diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go index d30f49cabcf..20f73c89ecb 100644 --- a/adapters/pulsepoint/pulsepoint_test.go +++ b/adapters/pulsepoint/pulsepoint_test.go @@ -3,9 +3,9 @@ package pulsepoint import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/pwbid/params_test.go b/adapters/pwbid/params_test.go index e16fd13c4dc..44d36379cca 100644 --- a/adapters/pwbid/params_test.go +++ b/adapters/pwbid/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/pwbid/pwbid.go b/adapters/pwbid/pwbid.go index 21b564ff124..2452c08a863 100644 --- a/adapters/pwbid/pwbid.go +++ b/adapters/pwbid/pwbid.go @@ -5,10 +5,10 @@ import ( "fmt" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/pwbid/pwbid_test.go b/adapters/pwbid/pwbid_test.go index 194e4bdea02..9abad59d97c 100644 --- a/adapters/pwbid/pwbid_test.go +++ b/adapters/pwbid/pwbid_test.go @@ -3,9 +3,9 @@ package pwbid import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/response.go b/adapters/response.go index 8b7d59bd2c0..747b6ddf9c0 100644 --- a/adapters/response.go +++ b/adapters/response.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" ) func CheckResponseStatusCodeForErrors(response *ResponseData) error { diff --git a/adapters/response_test.go b/adapters/response_test.go index 0651ff2646f..11ef1abcc5a 100644 --- a/adapters/response_test.go +++ b/adapters/response_test.go @@ -3,7 +3,7 @@ package adapters import ( "testing" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" "github.com/stretchr/testify/assert" ) diff --git a/adapters/revcontent/revcontent.go b/adapters/revcontent/revcontent.go index b825708a72b..b53f8962d4d 100644 --- a/adapters/revcontent/revcontent.go +++ b/adapters/revcontent/revcontent.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/revcontent/revcontent_test.go b/adapters/revcontent/revcontent_test.go index c4816a09a0c..a39a78a81dd 100644 --- a/adapters/revcontent/revcontent_test.go +++ b/adapters/revcontent/revcontent_test.go @@ -3,9 +3,9 @@ package revcontent import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/richaudience/params_test.go b/adapters/richaudience/params_test.go index 038936f3cbf..4f7ede9bd55 100644 --- a/adapters/richaudience/params_test.go +++ b/adapters/richaudience/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/richaudience/richaudience.go b/adapters/richaudience/richaudience.go index 16fbe229acf..2d976cc7c7b 100644 --- a/adapters/richaudience/richaudience.go +++ b/adapters/richaudience/richaudience.go @@ -7,10 +7,10 @@ import ( "net/url" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/richaudience/richaudience_test.go b/adapters/richaudience/richaudience_test.go index 30d04775c44..ee1ece29a91 100644 --- a/adapters/richaudience/richaudience_test.go +++ b/adapters/richaudience/richaudience_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/rise/rise.go b/adapters/rise/rise.go index e18b7c93852..56fdc4a8fff 100644 --- a/adapters/rise/rise.go +++ b/adapters/rise/rise.go @@ -9,9 +9,9 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // adapter is a Rise implementation of the adapters.Bidder interface. diff --git a/adapters/rise/rise_test.go b/adapters/rise/rise_test.go index 1ba3f8a865d..79d34bde64f 100644 --- a/adapters/rise/rise_test.go +++ b/adapters/rise/rise_test.go @@ -3,9 +3,9 @@ package rise import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsDir = "risetest" diff --git a/adapters/rtbhouse/rtbhouse.go b/adapters/rtbhouse/rtbhouse.go index dc67df9aa2b..089634cbd86 100644 --- a/adapters/rtbhouse/rtbhouse.go +++ b/adapters/rtbhouse/rtbhouse.go @@ -7,10 +7,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/rtbhouse/rtbhouse_test.go b/adapters/rtbhouse/rtbhouse_test.go index e367b921957..7bccfa36266 100644 --- a/adapters/rtbhouse/rtbhouse_test.go +++ b/adapters/rtbhouse/rtbhouse_test.go @@ -3,9 +3,9 @@ package rtbhouse import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsDir = "rtbhousetest" diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 635f6e14b42..7d09224e8b4 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -8,11 +8,11 @@ import ( "strconv" "strings" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/maputil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/maputil" "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/adcom1" diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 893cceb8cf7..334a5e269c0 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -8,11 +8,11 @@ import ( "strconv" "testing" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/adcom1" diff --git a/adapters/sa_lunamedia/params_test.go b/adapters/sa_lunamedia/params_test.go index 070c97741b0..568ac83d9cf 100644 --- a/adapters/sa_lunamedia/params_test.go +++ b/adapters/sa_lunamedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/sa_lunamedia/salunamedia.go b/adapters/sa_lunamedia/salunamedia.go index 9c9c34a6bf3..0d7d253114b 100644 --- a/adapters/sa_lunamedia/salunamedia.go +++ b/adapters/sa_lunamedia/salunamedia.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/sa_lunamedia/salunamedia_test.go b/adapters/sa_lunamedia/salunamedia_test.go index 9aa15187ab1..4bcfb96f28e 100644 --- a/adapters/sa_lunamedia/salunamedia_test.go +++ b/adapters/sa_lunamedia/salunamedia_test.go @@ -3,9 +3,9 @@ package salunamedia import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/screencore/params_test.go b/adapters/screencore/params_test.go index faa2f854928..7220f9945cf 100644 --- a/adapters/screencore/params_test.go +++ b/adapters/screencore/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/screencore/screencore.go b/adapters/screencore/screencore.go index 668e8b5e131..d5c2899af20 100644 --- a/adapters/screencore/screencore.go +++ b/adapters/screencore/screencore.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/screencore/screencore_test.go b/adapters/screencore/screencore_test.go index 6ee7f3ad554..4dc22b3cd6a 100644 --- a/adapters/screencore/screencore_test.go +++ b/adapters/screencore/screencore_test.go @@ -3,9 +3,9 @@ package screencore import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/seedingAlliance/params_test.go b/adapters/seedingAlliance/params_test.go index cdf29aed9e7..03817cf2d8f 100644 --- a/adapters/seedingAlliance/params_test.go +++ b/adapters/seedingAlliance/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/seedingAlliance/seedingAlliance.go b/adapters/seedingAlliance/seedingAlliance.go index 683cb6db8b7..b470dc757a5 100644 --- a/adapters/seedingAlliance/seedingAlliance.go +++ b/adapters/seedingAlliance/seedingAlliance.go @@ -8,10 +8,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/seedingAlliance/seedingAlliance_test.go b/adapters/seedingAlliance/seedingAlliance_test.go index 6946ead03ca..9d061e5feb6 100644 --- a/adapters/seedingAlliance/seedingAlliance_test.go +++ b/adapters/seedingAlliance/seedingAlliance_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/sharethrough/params_test.go b/adapters/sharethrough/params_test.go index 4edae1246d9..3c2d80dfa8b 100644 --- a/adapters/sharethrough/params_test.go +++ b/adapters/sharethrough/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/sharethrough/sharethrough.go b/adapters/sharethrough/sharethrough.go index 009ce0aa1d5..7287ca6b86e 100644 --- a/adapters/sharethrough/sharethrough.go +++ b/adapters/sharethrough/sharethrough.go @@ -7,11 +7,11 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/version" ) var adapterVersion = "10.0" diff --git a/adapters/sharethrough/sharethrough_test.go b/adapters/sharethrough/sharethrough_test.go index 4aab8fc56cc..2983487ac40 100644 --- a/adapters/sharethrough/sharethrough_test.go +++ b/adapters/sharethrough/sharethrough_test.go @@ -3,9 +3,9 @@ package sharethrough import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/silvermob/params_test.go b/adapters/silvermob/params_test.go index 13009f6a08b..d69171ed78d 100644 --- a/adapters/silvermob/params_test.go +++ b/adapters/silvermob/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the silvermob schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/silvermob/silvermob.go b/adapters/silvermob/silvermob.go index d4a1b55b45f..eaa45cb4be0 100644 --- a/adapters/silvermob/silvermob.go +++ b/adapters/silvermob/silvermob.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type SilverMobAdapter struct { diff --git a/adapters/silvermob/silvermob_test.go b/adapters/silvermob/silvermob_test.go index 5b7d60e2ead..ce08651ff59 100644 --- a/adapters/silvermob/silvermob_test.go +++ b/adapters/silvermob/silvermob_test.go @@ -3,9 +3,9 @@ package silvermob import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/silverpush/params_test.go b/adapters/silverpush/params_test.go index 8003b49ac3d..2a20aa2ff71 100644 --- a/adapters/silverpush/params_test.go +++ b/adapters/silverpush/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file intends to test static/bidder-params/silverpush.json diff --git a/adapters/silverpush/silverpush.go b/adapters/silverpush/silverpush.go index b5726cb28b7..805ef96657a 100644 --- a/adapters/silverpush/silverpush.go +++ b/adapters/silverpush/silverpush.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/silverpush/silverpush_test.go b/adapters/silverpush/silverpush_test.go index 87aa2867061..630539c0697 100644 --- a/adapters/silverpush/silverpush_test.go +++ b/adapters/silverpush/silverpush_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/smaato/image.go b/adapters/smaato/image.go index a4dad157bd1..8e601187ccd 100644 --- a/adapters/smaato/image.go +++ b/adapters/smaato/image.go @@ -3,9 +3,10 @@ package smaato import ( "encoding/json" "fmt" - "github.com/prebid/prebid-server/errortypes" "net/url" "strings" + + "github.com/prebid/prebid-server/v2/errortypes" ) type imageAd struct { diff --git a/adapters/smaato/native.go b/adapters/smaato/native.go index d0d40d35c57..b18a5fc4490 100644 --- a/adapters/smaato/native.go +++ b/adapters/smaato/native.go @@ -3,7 +3,8 @@ package smaato import ( "encoding/json" "fmt" - "github.com/prebid/prebid-server/errortypes" + + "github.com/prebid/prebid-server/v2/errortypes" ) type nativeAd struct { diff --git a/adapters/smaato/params_test.go b/adapters/smaato/params_test.go index 2e29550a394..d1c334acbfa 100644 --- a/adapters/smaato/params_test.go +++ b/adapters/smaato/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file intends to test static/bidder-params/smaato.json diff --git a/adapters/smaato/richmedia.go b/adapters/smaato/richmedia.go index a8865361d38..09e1f2bf3d6 100644 --- a/adapters/smaato/richmedia.go +++ b/adapters/smaato/richmedia.go @@ -3,9 +3,10 @@ package smaato import ( "encoding/json" "fmt" - "github.com/prebid/prebid-server/errortypes" "net/url" "strings" + + "github.com/prebid/prebid-server/v2/errortypes" ) type richMediaAd struct { diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go index e0892ef50fb..ffdeca06d7a 100644 --- a/adapters/smaato/smaato.go +++ b/adapters/smaato/smaato.go @@ -9,12 +9,12 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/timeutil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/timeutil" ) const clientVersion = "prebid_server_0.6" diff --git a/adapters/smaato/smaato_test.go b/adapters/smaato/smaato_test.go index a9caf86fe65..0d7d39027ea 100644 --- a/adapters/smaato/smaato_test.go +++ b/adapters/smaato/smaato_test.go @@ -9,10 +9,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/smartadserver/params_test.go b/adapters/smartadserver/params_test.go index ec4a7f7ec6c..fcd07278be6 100644 --- a/adapters/smartadserver/params_test.go +++ b/adapters/smartadserver/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/smartadserver.json diff --git a/adapters/smartadserver/smartadserver.go b/adapters/smartadserver/smartadserver.go index ecc26f9a1cc..ce14533f78b 100644 --- a/adapters/smartadserver/smartadserver.go +++ b/adapters/smartadserver/smartadserver.go @@ -9,10 +9,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type SmartAdserverAdapter struct { diff --git a/adapters/smartadserver/smartadserver_test.go b/adapters/smartadserver/smartadserver_test.go index f0a4ae8b48c..be5c13d54e2 100644 --- a/adapters/smartadserver/smartadserver_test.go +++ b/adapters/smartadserver/smartadserver_test.go @@ -3,9 +3,9 @@ package smartadserver import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/smarthub/params_test.go b/adapters/smarthub/params_test.go index 7797d4a82c9..3d9a6c351bc 100644 --- a/adapters/smarthub/params_test.go +++ b/adapters/smarthub/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/smarthub/smarthub.go b/adapters/smarthub/smarthub.go index 018f51aabc7..877987dd2e0 100644 --- a/adapters/smarthub/smarthub.go +++ b/adapters/smarthub/smarthub.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/smarthub/smarthub_test.go b/adapters/smarthub/smarthub_test.go index 837d219c9fd..d231f77703a 100644 --- a/adapters/smarthub/smarthub_test.go +++ b/adapters/smarthub/smarthub_test.go @@ -3,9 +3,9 @@ package smarthub import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/smartrtb/smartrtb.go b/adapters/smartrtb/smartrtb.go index ffc9854670c..875d9fc2aac 100644 --- a/adapters/smartrtb/smartrtb.go +++ b/adapters/smartrtb/smartrtb.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Base adapter structure. diff --git a/adapters/smartrtb/smartrtb_test.go b/adapters/smartrtb/smartrtb_test.go index b80955e6401..c0e83d3b826 100644 --- a/adapters/smartrtb/smartrtb_test.go +++ b/adapters/smartrtb/smartrtb_test.go @@ -3,9 +3,9 @@ package smartrtb import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/smartx/params_test.go b/adapters/smartx/params_test.go index fd28f4ead9b..81ee269a5ef 100644 --- a/adapters/smartx/params_test.go +++ b/adapters/smartx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/smartx/smartx.go b/adapters/smartx/smartx.go index 6fbc94968ca..7b928ec198c 100644 --- a/adapters/smartx/smartx.go +++ b/adapters/smartx/smartx.go @@ -7,9 +7,9 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/smartx/smartx_test.go b/adapters/smartx/smartx_test.go index ba6f6ba7762..503a547d2fc 100644 --- a/adapters/smartx/smartx_test.go +++ b/adapters/smartx/smartx_test.go @@ -3,9 +3,9 @@ package smartx import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsDir = "smartxtest" diff --git a/adapters/smartyads/params_test.go b/adapters/smartyads/params_test.go index 3aa5c0e837d..0e1b7186397 100644 --- a/adapters/smartyads/params_test.go +++ b/adapters/smartyads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/smartyads/smartyads.go b/adapters/smartyads/smartyads.go index c4455a62ccf..ce50e926444 100644 --- a/adapters/smartyads/smartyads.go +++ b/adapters/smartyads/smartyads.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type SmartyAdsAdapter struct { diff --git a/adapters/smartyads/smartyads_test.go b/adapters/smartyads/smartyads_test.go index b7edf2cadd2..6a697c1d5f4 100644 --- a/adapters/smartyads/smartyads_test.go +++ b/adapters/smartyads/smartyads_test.go @@ -3,9 +3,9 @@ package smartyads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/smilewanted/params_test.go b/adapters/smilewanted/params_test.go index 3217cafabab..ac055542417 100644 --- a/adapters/smilewanted/params_test.go +++ b/adapters/smilewanted/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/smilewanted.json diff --git a/adapters/smilewanted/smilewanted.go b/adapters/smilewanted/smilewanted.go index 34ffdf20711..93278c2c25e 100644 --- a/adapters/smilewanted/smilewanted.go +++ b/adapters/smilewanted/smilewanted.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/smilewanted/smilewanted_test.go b/adapters/smilewanted/smilewanted_test.go index 73fe15387c6..3df23f42911 100644 --- a/adapters/smilewanted/smilewanted_test.go +++ b/adapters/smilewanted/smilewanted_test.go @@ -3,9 +3,9 @@ package smilewanted import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/sonobi/params_test.go b/adapters/sonobi/params_test.go index 46c31015dae..c84a99edfdf 100644 --- a/adapters/sonobi/params_test.go +++ b/adapters/sonobi/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/sonobi.json diff --git a/adapters/sonobi/sonobi.go b/adapters/sonobi/sonobi.go index 2b2a04c27da..f85e6959464 100644 --- a/adapters/sonobi/sonobi.go +++ b/adapters/sonobi/sonobi.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // SonobiAdapter - Sonobi SonobiAdapter definition diff --git a/adapters/sonobi/sonobi_test.go b/adapters/sonobi/sonobi_test.go index 8e9790bd6f0..3123781323d 100644 --- a/adapters/sonobi/sonobi_test.go +++ b/adapters/sonobi/sonobi_test.go @@ -3,9 +3,9 @@ package sonobi import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index 15b07800b78..849e9c0b5d0 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -8,10 +8,10 @@ import ( "strconv" "strings" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/openrtb/v19/openrtb2" ) diff --git a/adapters/sovrn/sovrn_test.go b/adapters/sovrn/sovrn_test.go index 4a382d9b58e..1e041933e6c 100644 --- a/adapters/sovrn/sovrn_test.go +++ b/adapters/sovrn/sovrn_test.go @@ -3,9 +3,9 @@ package sovrn import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/sspBC/sspbc.go b/adapters/sspBC/sspbc.go index 6b601c120e0..f1a91322999 100644 --- a/adapters/sspBC/sspbc.go +++ b/adapters/sspBC/sspbc.go @@ -11,10 +11,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/sspBC/sspbc_test.go b/adapters/sspBC/sspbc_test.go index 47a8ae5d183..3c1f931bfb3 100644 --- a/adapters/sspBC/sspbc_test.go +++ b/adapters/sspBC/sspbc_test.go @@ -3,9 +3,9 @@ package sspBC import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/stroeerCore/params_test.go b/adapters/stroeerCore/params_test.go index 92586189b6f..ac8075c2251 100644 --- a/adapters/stroeerCore/params_test.go +++ b/adapters/stroeerCore/params_test.go @@ -2,8 +2,9 @@ package stroeerCore import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/stroeerCore/stroeercore.go b/adapters/stroeerCore/stroeercore.go index 08ba83a9544..5f0194061a8 100644 --- a/adapters/stroeerCore/stroeercore.go +++ b/adapters/stroeerCore/stroeercore.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/stroeerCore/stroeercore_test.go b/adapters/stroeerCore/stroeercore_test.go index fc7a680add0..153f3137c07 100644 --- a/adapters/stroeerCore/stroeercore_test.go +++ b/adapters/stroeerCore/stroeercore_test.go @@ -3,9 +3,9 @@ package stroeerCore import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/suntContent/params_test.go b/adapters/suntContent/params_test.go index 653ed948d46..a0da79df48a 100644 --- a/adapters/suntContent/params_test.go +++ b/adapters/suntContent/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/suntContent/suntContent.go b/adapters/suntContent/suntContent.go index f5440737bd9..d9dd590e0aa 100644 --- a/adapters/suntContent/suntContent.go +++ b/adapters/suntContent/suntContent.go @@ -8,10 +8,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/suntContent/suntContent_test.go b/adapters/suntContent/suntContent_test.go index 52be77efb60..f356f85a5eb 100644 --- a/adapters/suntContent/suntContent_test.go +++ b/adapters/suntContent/suntContent_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/taboola/params_test.go b/adapters/taboola/params_test.go index 51a9833cdcb..adcaa0334f7 100644 --- a/adapters/taboola/params_test.go +++ b/adapters/taboola/params_test.go @@ -2,8 +2,9 @@ package taboola import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/taboola/taboola.go b/adapters/taboola/taboola.go index 087f661104f..8f4e6220143 100644 --- a/adapters/taboola/taboola.go +++ b/adapters/taboola/taboola.go @@ -11,11 +11,11 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/taboola/taboola_test.go b/adapters/taboola/taboola_test.go index 320d08da22f..bd674440150 100644 --- a/adapters/taboola/taboola_test.go +++ b/adapters/taboola/taboola_test.go @@ -1,14 +1,12 @@ package taboola import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/stretchr/testify/assert" "testing" -) -import ( - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/tappx/params_test.go b/adapters/tappx/params_test.go index 8a248345994..ddfcbeb021f 100644 --- a/adapters/tappx/params_test.go +++ b/adapters/tappx/params_test.go @@ -2,8 +2,9 @@ package tappx import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go index d66a10a2bbe..d05a59316ae 100644 --- a/adapters/tappx/tappx.go +++ b/adapters/tappx/tappx.go @@ -11,11 +11,11 @@ import ( "time" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const TAPPX_BIDDER_VERSION = "1.5" diff --git a/adapters/tappx/tappx_test.go b/adapters/tappx/tappx_test.go index c1b711426fb..b3c6f3fe625 100644 --- a/adapters/tappx/tappx_test.go +++ b/adapters/tappx/tappx_test.go @@ -4,9 +4,9 @@ import ( "regexp" "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/teads/teads.go b/adapters/teads/teads.go index 9c2bb57fc57..72ee97f5a6e 100644 --- a/adapters/teads/teads.go +++ b/adapters/teads/teads.go @@ -9,11 +9,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Builder builds a new instance of the Teads adapter for the given bidder with the given config. diff --git a/adapters/teads/teads_test.go b/adapters/teads/teads_test.go index 791ce7aed6f..4f492b66b1e 100644 --- a/adapters/teads/teads_test.go +++ b/adapters/teads/teads_test.go @@ -1,11 +1,12 @@ package teads import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/telaria/params_test.go b/adapters/telaria/params_test.go index efa3fba1be9..9e451ca091e 100644 --- a/adapters/telaria/params_test.go +++ b/adapters/telaria/params_test.go @@ -2,8 +2,9 @@ package telaria import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/telaria/telaria.go b/adapters/telaria/telaria.go index bbe600178f4..a1ac5611e1f 100644 --- a/adapters/telaria/telaria.go +++ b/adapters/telaria/telaria.go @@ -7,10 +7,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const Endpoint = "https://ads.tremorhub.com/ad/rtb/prebid" diff --git a/adapters/telaria/telaria_test.go b/adapters/telaria/telaria_test.go index f8008835ac3..3c7d1bea46e 100644 --- a/adapters/telaria/telaria_test.go +++ b/adapters/telaria/telaria_test.go @@ -3,9 +3,9 @@ package telaria import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/tpmn/params_test.go b/adapters/tpmn/params_test.go index 7bd7c478638..4715d910855 100644 --- a/adapters/tpmn/params_test.go +++ b/adapters/tpmn/params_test.go @@ -2,8 +2,9 @@ package tpmn import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/tpmn/tpmn.go b/adapters/tpmn/tpmn.go index 7afe94e5f79..443b8837bac 100644 --- a/adapters/tpmn/tpmn.go +++ b/adapters/tpmn/tpmn.go @@ -7,9 +7,9 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TpmnAdapter struct diff --git a/adapters/tpmn/tpmn_test.go b/adapters/tpmn/tpmn_test.go index 6fbd85936f1..7170dbb3d5f 100644 --- a/adapters/tpmn/tpmn_test.go +++ b/adapters/tpmn/tpmn_test.go @@ -3,9 +3,9 @@ package tpmn import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/trafficgate/params_test.go b/adapters/trafficgate/params_test.go index 4dc2c792bc9..adc11c08335 100644 --- a/adapters/trafficgate/params_test.go +++ b/adapters/trafficgate/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the trafficgate schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/trafficgate/trafficgate.go b/adapters/trafficgate/trafficgate.go index 3c9ebe9ba98..1462fe59bd5 100644 --- a/adapters/trafficgate/trafficgate.go +++ b/adapters/trafficgate/trafficgate.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/trafficgate/trafficgate_test.go b/adapters/trafficgate/trafficgate_test.go index 326c50523fe..473c9d5d5c3 100644 --- a/adapters/trafficgate/trafficgate_test.go +++ b/adapters/trafficgate/trafficgate_test.go @@ -3,9 +3,9 @@ package trafficgate import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/triplelift/triplelift.go b/adapters/triplelift/triplelift.go index 0e7fbe4a462..793647bccaa 100644 --- a/adapters/triplelift/triplelift.go +++ b/adapters/triplelift/triplelift.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type TripleliftAdapter struct { diff --git a/adapters/triplelift/triplelift_test.go b/adapters/triplelift/triplelift_test.go index add71b05788..c4468a93faa 100644 --- a/adapters/triplelift/triplelift_test.go +++ b/adapters/triplelift/triplelift_test.go @@ -3,9 +3,9 @@ package triplelift import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/triplelift_native/triplelift_native.go b/adapters/triplelift_native/triplelift_native.go index 9131c79a975..08aefb3b135 100644 --- a/adapters/triplelift_native/triplelift_native.go +++ b/adapters/triplelift_native/triplelift_native.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type TripleliftNativeAdapter struct { diff --git a/adapters/triplelift_native/triplelift_native_test.go b/adapters/triplelift_native/triplelift_native_test.go index 18e157a41cd..c1c82501b32 100644 --- a/adapters/triplelift_native/triplelift_native_test.go +++ b/adapters/triplelift_native/triplelift_native_test.go @@ -3,9 +3,9 @@ package triplelift_native import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/ucfunnel/params_test.go b/adapters/ucfunnel/params_test.go index b721925e72a..9bba397a084 100644 --- a/adapters/ucfunnel/params_test.go +++ b/adapters/ucfunnel/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/ucfunnel.json diff --git a/adapters/ucfunnel/ucfunnel.go b/adapters/ucfunnel/ucfunnel.go index a0d86a0fa29..cb4a6093c99 100644 --- a/adapters/ucfunnel/ucfunnel.go +++ b/adapters/ucfunnel/ucfunnel.go @@ -7,10 +7,10 @@ import ( "net/url" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type UcfunnelAdapter struct { diff --git a/adapters/ucfunnel/ucfunnel_test.go b/adapters/ucfunnel/ucfunnel_test.go index a906b9279e8..6e5000c3205 100644 --- a/adapters/ucfunnel/ucfunnel_test.go +++ b/adapters/ucfunnel/ucfunnel_test.go @@ -6,9 +6,9 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestMakeRequests(t *testing.T) { diff --git a/adapters/undertone/params_test.go b/adapters/undertone/params_test.go index b48d08188d5..3144f757078 100644 --- a/adapters/undertone/params_test.go +++ b/adapters/undertone/params_test.go @@ -2,8 +2,9 @@ package undertone import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/undertone/undertone.go b/adapters/undertone/undertone.go index 7f8dde35abb..a5f428b12ca 100644 --- a/adapters/undertone/undertone.go +++ b/adapters/undertone/undertone.go @@ -8,10 +8,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const adapterId = 4 diff --git a/adapters/undertone/undertone_test.go b/adapters/undertone/undertone_test.go index d7e6d52339b..c08460e8627 100644 --- a/adapters/undertone/undertone_test.go +++ b/adapters/undertone/undertone_test.go @@ -1,10 +1,11 @@ package undertone import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/unicorn/params_test.go b/adapters/unicorn/params_test.go index 9313183fbfa..fd76995d1c0 100644 --- a/adapters/unicorn/params_test.go +++ b/adapters/unicorn/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/unicorn/unicorn.go b/adapters/unicorn/unicorn.go index 5048d9d2394..6351ad059cf 100644 --- a/adapters/unicorn/unicorn.go +++ b/adapters/unicorn/unicorn.go @@ -8,10 +8,10 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/unicorn/unicorn_test.go b/adapters/unicorn/unicorn_test.go index 084be78498a..6c1e5aa73f2 100644 --- a/adapters/unicorn/unicorn_test.go +++ b/adapters/unicorn/unicorn_test.go @@ -3,9 +3,9 @@ package unicorn import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/unruly/params_test.go b/adapters/unruly/params_test.go index e9607358a59..f8feea872c8 100644 --- a/adapters/unruly/params_test.go +++ b/adapters/unruly/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/unruly/unruly.go b/adapters/unruly/unruly.go index 1f4bf6b0203..b96da9eb93e 100644 --- a/adapters/unruly/unruly.go +++ b/adapters/unruly/unruly.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/unruly/unruly_test.go b/adapters/unruly/unruly_test.go index b5d837abea5..8407ba15212 100644 --- a/adapters/unruly/unruly_test.go +++ b/adapters/unruly/unruly_test.go @@ -3,9 +3,9 @@ package unruly import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/videobyte/params_test.go b/adapters/videobyte/params_test.go index b638d4585c6..dbc815fd76d 100644 --- a/adapters/videobyte/params_test.go +++ b/adapters/videobyte/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/videobyte.json diff --git a/adapters/videobyte/videobyte.go b/adapters/videobyte/videobyte.go index 2dc6df84895..afbce1376f9 100644 --- a/adapters/videobyte/videobyte.go +++ b/adapters/videobyte/videobyte.go @@ -6,10 +6,10 @@ import ( "net/http" "net/url" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/openrtb/v19/openrtb2" ) diff --git a/adapters/videobyte/videobyte_test.go b/adapters/videobyte/videobyte_test.go index d4dda0606f8..9e566a20ef2 100644 --- a/adapters/videobyte/videobyte_test.go +++ b/adapters/videobyte/videobyte_test.go @@ -3,9 +3,9 @@ package videobyte import ( "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" - "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/videoheroes/params_test.go b/adapters/videoheroes/params_test.go index d79f83245a4..66e1e6a2788 100644 --- a/adapters/videoheroes/params_test.go +++ b/adapters/videoheroes/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/videoheroes/videoheroes.go b/adapters/videoheroes/videoheroes.go index d4efcab2c90..0014c1613e4 100755 --- a/adapters/videoheroes/videoheroes.go +++ b/adapters/videoheroes/videoheroes.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/videoheroes/videoheroes_test.go b/adapters/videoheroes/videoheroes_test.go index ac60d56e175..7c0b2268f51 100644 --- a/adapters/videoheroes/videoheroes_test.go +++ b/adapters/videoheroes/videoheroes_test.go @@ -3,9 +3,9 @@ package videoheroes import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/vidoomy/params_test.go b/adapters/vidoomy/params_test.go index 63ffb462c19..40c17029f9e 100644 --- a/adapters/vidoomy/params_test.go +++ b/adapters/vidoomy/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/vidoomy.json diff --git a/adapters/vidoomy/vidoomy.go b/adapters/vidoomy/vidoomy.go index 7e7e9d64eb3..09e924e596c 100644 --- a/adapters/vidoomy/vidoomy.go +++ b/adapters/vidoomy/vidoomy.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/vidoomy/vidoomy_test.go b/adapters/vidoomy/vidoomy_test.go index 60cd2c9d967..7acc477ae1c 100644 --- a/adapters/vidoomy/vidoomy_test.go +++ b/adapters/vidoomy/vidoomy_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestVidoomyBidderEndpointConfig(t *testing.T) { diff --git a/adapters/visiblemeasures/params_test.go b/adapters/visiblemeasures/params_test.go index 7bcc1cf60cf..ed74ef1ad35 100644 --- a/adapters/visiblemeasures/params_test.go +++ b/adapters/visiblemeasures/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/visiblemeasures/visiblemeasures.go b/adapters/visiblemeasures/visiblemeasures.go index 3d6a96640e9..7b8cb9a9dd3 100644 --- a/adapters/visiblemeasures/visiblemeasures.go +++ b/adapters/visiblemeasures/visiblemeasures.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/visiblemeasures/visiblemeasures_test.go b/adapters/visiblemeasures/visiblemeasures_test.go index 8c1759c010e..8970ccb1e43 100644 --- a/adapters/visiblemeasures/visiblemeasures_test.go +++ b/adapters/visiblemeasures/visiblemeasures_test.go @@ -3,9 +3,9 @@ package visiblemeasures import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/visx/params_test.go b/adapters/visx/params_test.go index e53d2cda007..0646d221e27 100644 --- a/adapters/visx/params_test.go +++ b/adapters/visx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/visx/visx.go b/adapters/visx/visx.go index 713c2693990..a7cc232c01a 100644 --- a/adapters/visx/visx.go +++ b/adapters/visx/visx.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type VisxAdapter struct { diff --git a/adapters/visx/visx_test.go b/adapters/visx/visx_test.go index 5fc58a1f83d..8cdccc2e653 100644 --- a/adapters/visx/visx_test.go +++ b/adapters/visx/visx_test.go @@ -3,9 +3,9 @@ package visx import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/vox/params_test.go b/adapters/vox/params_test.go index be148d3b32d..e23a57d9b30 100644 --- a/adapters/vox/params_test.go +++ b/adapters/vox/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/vox/vox.go b/adapters/vox/vox.go index 0b56fcbf9d7..740fe84e611 100644 --- a/adapters/vox/vox.go +++ b/adapters/vox/vox.go @@ -3,10 +3,11 @@ package vox import ( "encoding/json" "fmt" + "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/vox/vox_test.go b/adapters/vox/vox_test.go index 95d11a8ad79..dfb345b2e02 100644 --- a/adapters/vox/vox_test.go +++ b/adapters/vox/vox_test.go @@ -3,9 +3,9 @@ package vox import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/vrtcal/params_test.go b/adapters/vrtcal/params_test.go index d45d3b39013..0e30dd6fcc9 100644 --- a/adapters/vrtcal/params_test.go +++ b/adapters/vrtcal/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) //Vrtcal doesn't currently require any custom fields. This file is included for conformity only diff --git a/adapters/vrtcal/vrtcal.go b/adapters/vrtcal/vrtcal.go index ab47eddb441..01ef178b352 100644 --- a/adapters/vrtcal/vrtcal.go +++ b/adapters/vrtcal/vrtcal.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type VrtcalAdapter struct { diff --git a/adapters/vrtcal/vrtcal_test.go b/adapters/vrtcal/vrtcal_test.go index 31e6c78e2c1..a4ba917922f 100644 --- a/adapters/vrtcal/vrtcal_test.go +++ b/adapters/vrtcal/vrtcal_test.go @@ -3,9 +3,9 @@ package vrtcal import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/xeworks/params_test.go b/adapters/xeworks/params_test.go index 68d36096049..1c14b3a0989 100644 --- a/adapters/xeworks/params_test.go +++ b/adapters/xeworks/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/xeworks/xeworks.go b/adapters/xeworks/xeworks.go index 35e551b1034..e892fcfc932 100644 --- a/adapters/xeworks/xeworks.go +++ b/adapters/xeworks/xeworks.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type bidType struct { diff --git a/adapters/xeworks/xeworks_test.go b/adapters/xeworks/xeworks_test.go index 4869a05a229..db7e26c9bae 100644 --- a/adapters/xeworks/xeworks_test.go +++ b/adapters/xeworks/xeworks_test.go @@ -3,9 +3,9 @@ package xeworks import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/yahooAds/params_test.go b/adapters/yahooAds/params_test.go index c0deaaa32c9..dbbc2c84adb 100644 --- a/adapters/yahooAds/params_test.go +++ b/adapters/yahooAds/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/yahooAds.json diff --git a/adapters/yahooAds/yahooAds.go b/adapters/yahooAds/yahooAds.go index 3597d0e359c..241f4eba506 100644 --- a/adapters/yahooAds/yahooAds.go +++ b/adapters/yahooAds/yahooAds.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/yahooAds/yahooAds_test.go b/adapters/yahooAds/yahooAds_test.go index 924eabd5ec1..ae9103d141e 100644 --- a/adapters/yahooAds/yahooAds_test.go +++ b/adapters/yahooAds/yahooAds_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestYahooAdsBidderEndpointConfig(t *testing.T) { diff --git a/adapters/yeahmobi/params_test.go b/adapters/yeahmobi/params_test.go index 997bf93a53f..805be75da30 100644 --- a/adapters/yeahmobi/params_test.go +++ b/adapters/yeahmobi/params_test.go @@ -2,8 +2,9 @@ package yeahmobi import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/yeahmobi/yeahmobi.go b/adapters/yeahmobi/yeahmobi.go index ede25387ce2..35434d05473 100644 --- a/adapters/yeahmobi/yeahmobi.go +++ b/adapters/yeahmobi/yeahmobi.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/yeahmobi/yeahmobi_test.go b/adapters/yeahmobi/yeahmobi_test.go index 0b1c39ef214..c1c7be35105 100644 --- a/adapters/yeahmobi/yeahmobi_test.go +++ b/adapters/yeahmobi/yeahmobi_test.go @@ -3,9 +3,9 @@ package yeahmobi import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/yieldlab/params_test.go b/adapters/yieldlab/params_test.go index ed0d2863629..74a91ac3bf7 100644 --- a/adapters/yieldlab/params_test.go +++ b/adapters/yieldlab/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/yieldlab.json diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go index 74ee6bd7220..c6aa7f27dac 100644 --- a/adapters/yieldlab/yieldlab.go +++ b/adapters/yieldlab/yieldlab.go @@ -12,10 +12,10 @@ import ( "golang.org/x/text/currency" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // YieldlabAdapter connects the Yieldlab API to prebid server diff --git a/adapters/yieldlab/yieldlab_test.go b/adapters/yieldlab/yieldlab_test.go index d3fc9f3eb1d..4c4f8f56330 100644 --- a/adapters/yieldlab/yieldlab_test.go +++ b/adapters/yieldlab/yieldlab_test.go @@ -8,9 +8,9 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testURL = "https://ad.yieldlab.net/testing/" diff --git a/adapters/yieldmo/params_test.go b/adapters/yieldmo/params_test.go index d94c7ff035b..647c21abb90 100644 --- a/adapters/yieldmo/params_test.go +++ b/adapters/yieldmo/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/yieldmo.json diff --git a/adapters/yieldmo/yieldmo.go b/adapters/yieldmo/yieldmo.go index bf8410b294b..795d32b8846 100644 --- a/adapters/yieldmo/yieldmo.go +++ b/adapters/yieldmo/yieldmo.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type YieldmoAdapter struct { diff --git a/adapters/yieldmo/yieldmo_test.go b/adapters/yieldmo/yieldmo_test.go index 1d9426d0643..f89d4849a2c 100644 --- a/adapters/yieldmo/yieldmo_test.go +++ b/adapters/yieldmo/yieldmo_test.go @@ -3,9 +3,9 @@ package yieldmo import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/yieldone/params_test.go b/adapters/yieldone/params_test.go index 6048ea5d7dc..623928839ef 100644 --- a/adapters/yieldone/params_test.go +++ b/adapters/yieldone/params_test.go @@ -2,8 +2,9 @@ package yieldone import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/yieldone/yieldone.go b/adapters/yieldone/yieldone.go index 2d5f1d81173..e852c5cb6ba 100644 --- a/adapters/yieldone/yieldone.go +++ b/adapters/yieldone/yieldone.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type YieldoneAdapter struct { diff --git a/adapters/yieldone/yieldone_test.go b/adapters/yieldone/yieldone_test.go index 12d634d463d..1847ef9bf06 100644 --- a/adapters/yieldone/yieldone_test.go +++ b/adapters/yieldone/yieldone_test.go @@ -3,9 +3,9 @@ package yieldone import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/zeroclickfraud/zeroclickfraud.go b/adapters/zeroclickfraud/zeroclickfraud.go index 235f678d7bb..6f477352652 100644 --- a/adapters/zeroclickfraud/zeroclickfraud.go +++ b/adapters/zeroclickfraud/zeroclickfraud.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type ZeroClickFraudAdapter struct { diff --git a/adapters/zeroclickfraud/zeroclickfraud_test.go b/adapters/zeroclickfraud/zeroclickfraud_test.go index e07c43ff7a2..6e2a7e23b4c 100644 --- a/adapters/zeroclickfraud/zeroclickfraud_test.go +++ b/adapters/zeroclickfraud/zeroclickfraud_test.go @@ -3,9 +3,9 @@ package zeroclickfraud import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/zeta_global_ssp/zeta_global_ssp.go b/adapters/zeta_global_ssp/zeta_global_ssp.go index 7a5f3395724..dcfffc8b342 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp.go +++ b/adapters/zeta_global_ssp/zeta_global_ssp.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/zeta_global_ssp/zeta_global_ssp_test.go b/adapters/zeta_global_ssp/zeta_global_ssp_test.go index 3b7be288fa1..f5384cdec0f 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp_test.go +++ b/adapters/zeta_global_ssp/zeta_global_ssp_test.go @@ -3,9 +3,9 @@ package zeta_global_ssp import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adservertargeting/adservertargeting.go b/adservertargeting/adservertargeting.go index 94d64579e66..e79b521e1f5 100644 --- a/adservertargeting/adservertargeting.go +++ b/adservertargeting/adservertargeting.go @@ -7,7 +7,7 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type DataSource string diff --git a/adservertargeting/adservertargeting_test.go b/adservertargeting/adservertargeting_test.go index 0da3635d9b7..d0a90ce0813 100644 --- a/adservertargeting/adservertargeting_test.go +++ b/adservertargeting/adservertargeting_test.go @@ -7,8 +7,8 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/adservertargeting/reqcache.go b/adservertargeting/reqcache.go index b8b147af84e..f2a4636c2f8 100644 --- a/adservertargeting/reqcache.go +++ b/adservertargeting/reqcache.go @@ -5,7 +5,7 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) type requestCache struct { diff --git a/adservertargeting/requestlookup.go b/adservertargeting/requestlookup.go index debf19830db..d3284525b50 100644 --- a/adservertargeting/requestlookup.go +++ b/adservertargeting/requestlookup.go @@ -3,11 +3,12 @@ package adservertargeting import ( "encoding/json" "fmt" - "github.com/buger/jsonparser" - "github.com/pkg/errors" - "github.com/prebid/prebid-server/openrtb_ext" "net/url" "strings" + + "github.com/buger/jsonparser" + "github.com/pkg/errors" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func getAdServerTargeting(reqWrapper *openrtb_ext.RequestWrapper) ([]openrtb_ext.AdServerTarget, error) { diff --git a/adservertargeting/requestlookup_test.go b/adservertargeting/requestlookup_test.go index cd86364558e..1b54beb03e7 100644 --- a/adservertargeting/requestlookup_test.go +++ b/adservertargeting/requestlookup_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adservertargeting/respdataprocessor.go b/adservertargeting/respdataprocessor.go index 649f802f6e2..ab3ca070f89 100644 --- a/adservertargeting/respdataprocessor.go +++ b/adservertargeting/respdataprocessor.go @@ -7,8 +7,8 @@ import ( "github.com/pkg/errors" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) diff --git a/adservertargeting/respdataprocessor_test.go b/adservertargeting/respdataprocessor_test.go index 1118458e0f6..95404cef733 100644 --- a/adservertargeting/respdataprocessor_test.go +++ b/adservertargeting/respdataprocessor_test.go @@ -8,7 +8,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adservertargeting/utils.go b/adservertargeting/utils.go index 8093a4b6974..136f7900e24 100644 --- a/adservertargeting/utils.go +++ b/adservertargeting/utils.go @@ -1,11 +1,12 @@ package adservertargeting import ( + "strings" + "github.com/buger/jsonparser" "github.com/pkg/errors" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "strings" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func splitAndGet(path string, data []byte, delimiter string) (string, error) { diff --git a/amp/parse.go b/amp/parse.go index 34f1a3cacb4..a42ef6fa399 100644 --- a/amp/parse.go +++ b/amp/parse.go @@ -10,10 +10,10 @@ import ( tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/privacy/ccpa" + "github.com/prebid/prebid-server/v2/privacy/gdpr" ) // Params defines the parameters of an AMP request. diff --git a/amp/parse_test.go b/amp/parse_test.go index 9f981fd30e0..38c7a05a615 100644 --- a/amp/parse_test.go +++ b/amp/parse_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/privacy/ccpa" + "github.com/prebid/prebid-server/v2/privacy/gdpr" "github.com/stretchr/testify/assert" ) diff --git a/analytics/build/build.go b/analytics/build/build.go index 6fb48705981..7fc577daedf 100644 --- a/analytics/build/build.go +++ b/analytics/build/build.go @@ -3,12 +3,12 @@ package build import ( "github.com/benbjohnson/clock" "github.com/golang/glog" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/analytics/clients" - "github.com/prebid/prebid-server/analytics/filesystem" - "github.com/prebid/prebid-server/analytics/pubstack" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/analytics/clients" + "github.com/prebid/prebid-server/v2/analytics/filesystem" + "github.com/prebid/prebid-server/v2/analytics/pubstack" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/privacy" ) // Modules that need to be logged to need to be initialized here diff --git a/analytics/build/build_test.go b/analytics/build/build_test.go index dbd129a7afd..efc0c862564 100644 --- a/analytics/build/build_test.go +++ b/analytics/build/build_test.go @@ -1,15 +1,15 @@ package build import ( - "github.com/prebid/prebid-server/analytics" "net/http" "os" "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/analytics/core.go b/analytics/core.go index 0279ae83868..122a3da8ad3 100644 --- a/analytics/core.go +++ b/analytics/core.go @@ -4,9 +4,9 @@ import ( "time" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Module must be implemented by analytics modules to extract the required information and logging diff --git a/analytics/filesystem/file_module.go b/analytics/filesystem/file_module.go index 24b7be6b599..4f7886c1206 100644 --- a/analytics/filesystem/file_module.go +++ b/analytics/filesystem/file_module.go @@ -6,8 +6,8 @@ import ( "github.com/chasex/glog" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) type RequestType string diff --git a/analytics/filesystem/file_module_test.go b/analytics/filesystem/file_module_test.go index 9843a8ab108..f2f81bcdf77 100644 --- a/analytics/filesystem/file_module_test.go +++ b/analytics/filesystem/file_module_test.go @@ -6,8 +6,8 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" "github.com/prebid/openrtb/v19/openrtb2" ) diff --git a/analytics/filesystem/model.go b/analytics/filesystem/model.go index 9fc7a6e19a2..61987ed3b53 100644 --- a/analytics/filesystem/model.go +++ b/analytics/filesystem/model.go @@ -4,10 +4,10 @@ import ( "time" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type logAuction struct { diff --git a/analytics/pubstack/configupdate.go b/analytics/pubstack/configupdate.go index 622161a04f2..0ecaaef05c3 100644 --- a/analytics/pubstack/configupdate.go +++ b/analytics/pubstack/configupdate.go @@ -6,7 +6,7 @@ import ( "net/url" "time" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v2/util/task" ) // ConfigUpdateTask publishes configurations until the stop channel is signaled. diff --git a/analytics/pubstack/helpers/json.go b/analytics/pubstack/helpers/json.go index f56a4c6194b..c516cb56ae8 100644 --- a/analytics/pubstack/helpers/json.go +++ b/analytics/pubstack/helpers/json.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) func JsonifyAuctionObject(ao *analytics.AuctionObject, scope string) ([]byte, error) { diff --git a/analytics/pubstack/helpers/json_test.go b/analytics/pubstack/helpers/json_test.go index 07ead724929..7ec46e53f83 100644 --- a/analytics/pubstack/helpers/json_test.go +++ b/analytics/pubstack/helpers/json_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/v2/analytics" "github.com/stretchr/testify/assert" ) diff --git a/analytics/pubstack/helpers/model.go b/analytics/pubstack/helpers/model.go index 91b86d7fc86..1d6ede1c59b 100644 --- a/analytics/pubstack/helpers/model.go +++ b/analytics/pubstack/helpers/model.go @@ -4,10 +4,10 @@ import ( "time" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type logAuction struct { diff --git a/analytics/pubstack/pubstack_module.go b/analytics/pubstack/pubstack_module.go index f2d8726d356..535118c0000 100644 --- a/analytics/pubstack/pubstack_module.go +++ b/analytics/pubstack/pubstack_module.go @@ -12,9 +12,9 @@ import ( "github.com/benbjohnson/clock" "github.com/golang/glog" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/analytics/pubstack/eventchannel" - "github.com/prebid/prebid-server/analytics/pubstack/helpers" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/analytics/pubstack/eventchannel" + "github.com/prebid/prebid-server/v2/analytics/pubstack/helpers" ) type Configuration struct { diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index 23e110df9c1..911de4c6959 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -8,8 +8,8 @@ import ( "time" "github.com/benbjohnson/clock" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/analytics/runner.go b/analytics/runner.go index 08f67561f8a..7a2c56f77dd 100644 --- a/analytics/runner.go +++ b/analytics/runner.go @@ -1,7 +1,7 @@ package analytics import ( - "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/v2/privacy" ) type Runner interface { diff --git a/bidadjustment/apply.go b/bidadjustment/apply.go index 4fa3b737b16..16b8305a01a 100644 --- a/bidadjustment/apply.go +++ b/bidadjustment/apply.go @@ -3,8 +3,8 @@ package bidadjustment import ( "math" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/bidadjustment/apply_test.go b/bidadjustment/apply_test.go index c0eb74ab419..a1fa9f7c486 100644 --- a/bidadjustment/apply_test.go +++ b/bidadjustment/apply_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/bidadjustment/build_rules.go b/bidadjustment/build_rules.go index bccb3bc86cf..f028d78616a 100644 --- a/bidadjustment/build_rules.go +++ b/bidadjustment/build_rules.go @@ -1,8 +1,8 @@ package bidadjustment import ( - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/bidadjustment/build_rules_test.go b/bidadjustment/build_rules_test.go index 263a782130e..9ae1b477e8f 100644 --- a/bidadjustment/build_rules_test.go +++ b/bidadjustment/build_rules_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/bidadjustment/validate.go b/bidadjustment/validate.go index c0ae3d4a27b..f34ef48ba49 100644 --- a/bidadjustment/validate.go +++ b/bidadjustment/validate.go @@ -3,7 +3,7 @@ package bidadjustment import ( "math" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Validate checks whether all provided bid adjustments are valid or not against the requirements defined in the issue diff --git a/bidadjustment/validate_test.go b/bidadjustment/validate_test.go index a0b4eb436eb..caf4188bb5e 100644 --- a/bidadjustment/validate_test.go +++ b/bidadjustment/validate_test.go @@ -3,7 +3,7 @@ package bidadjustment import ( "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/config/account.go b/config/account.go index 92af6c8a97f..f1de54f79a2 100644 --- a/config/account.go +++ b/config/account.go @@ -7,8 +7,8 @@ import ( "strings" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/iputil" ) // ChannelType enumerates the values of integrations Prebid Server can configure for an account diff --git a/config/account_test.go b/config/account_test.go index d0c8507deef..648618f2706 100644 --- a/config/account_test.go +++ b/config/account_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/config/bidderinfo.go b/config/bidderinfo.go index f1f437c5c93..45abc85227a 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -9,8 +9,8 @@ import ( "strings" "text/template" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" validator "github.com/asaskevich/govalidator" "gopkg.in/yaml.v3" diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go index 7b134314609..ad242bd0a47 100644 --- a/config/bidderinfo_test.go +++ b/config/bidderinfo_test.go @@ -7,7 +7,7 @@ import ( "gopkg.in/yaml.v3" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/config/compression.go b/config/compression.go index db85202b4a8..2fe8e7b22ac 100644 --- a/config/compression.go +++ b/config/compression.go @@ -1,6 +1,6 @@ package config -import "github.com/prebid/prebid-server/util/httputil" +import "github.com/prebid/prebid-server/v2/util/httputil" type Compression struct { Request CompressionInfo `mapstructure:"request"` diff --git a/config/compression_test.go b/config/compression_test.go index cd9048cd99e..230d1912345 100644 --- a/config/compression_test.go +++ b/config/compression_test.go @@ -3,7 +3,7 @@ package config import ( "testing" - "github.com/prebid/prebid-server/util/httputil" + "github.com/prebid/prebid-server/v2/util/httputil" "github.com/stretchr/testify/assert" ) diff --git a/config/config.go b/config/config.go index 4bd7f807002..11b7eb5841e 100644 --- a/config/config.go +++ b/config/config.go @@ -12,9 +12,9 @@ import ( "github.com/golang/glog" "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/spf13/viper" ) diff --git a/config/config_test.go b/config/config_test.go index 21d1681c1e7..535b4e7b099 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -9,7 +9,7 @@ import ( "time" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/spf13/viper" "github.com/stretchr/testify/assert" ) diff --git a/currency/rate_converter.go b/currency/rate_converter.go index cda8d763048..a9ab9547f83 100644 --- a/currency/rate_converter.go +++ b/currency/rate_converter.go @@ -8,9 +8,9 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/timeutil" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/timeutil" ) // RateConverter holds the currencies conversion rates dictionary diff --git a/currency/rate_converter_test.go b/currency/rate_converter_test.go index 617aa02e96a..96003c7d986 100644 --- a/currency/rate_converter_test.go +++ b/currency/rate_converter_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v2/util/task" "github.com/stretchr/testify/assert" ) diff --git a/currency/rates_test.go b/currency/rates_test.go index 86c25d14ac0..254bc282dec 100644 --- a/currency/rates_test.go +++ b/currency/rates_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) func TestUnMarshallRates(t *testing.T) { diff --git a/currency/validation.go b/currency/validation.go index 7a0e2aa02bd..d6429c357b6 100644 --- a/currency/validation.go +++ b/currency/validation.go @@ -5,8 +5,8 @@ import ( "golang.org/x/text/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // ValidateCustomRates throws a bad input error if any of the 3-digit currency codes found in diff --git a/currency/validation_test.go b/currency/validation_test.go index d49b9824986..65f93a5f9e9 100644 --- a/currency/validation_test.go +++ b/currency/validation_test.go @@ -3,8 +3,8 @@ package currency import ( "testing" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index ff801de988f..bbf91330be5 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -14,21 +14,21 @@ import ( "github.com/julienschmidt/httprouter" gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - gppPrivacy "github.com/prebid/prebid-server/privacy/gpp" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/jsonutil" - stringutil "github.com/prebid/prebid-server/util/stringutil" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/privacy/ccpa" + gppPrivacy "github.com/prebid/prebid-server/v2/privacy/gpp" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/jsonutil" + stringutil "github.com/prebid/prebid-server/v2/util/stringutil" ) var ( diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 1b02357dc5d..956123006ca 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -12,17 +12,17 @@ import ( "testing" "testing/iotest" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/privacy/ccpa" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/endpoints/currency_rates.go b/endpoints/currency_rates.go index 49ae9963cd9..f08154471fe 100644 --- a/endpoints/currency_rates.go +++ b/endpoints/currency_rates.go @@ -5,8 +5,8 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // currencyRatesInfo holds currency rates information. diff --git a/endpoints/currency_rates_test.go b/endpoints/currency_rates_test.go index 7fc513e7dbe..0b953c640e2 100644 --- a/endpoints/currency_rates_test.go +++ b/endpoints/currency_rates_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/v2/currency" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/events/account_test.go b/endpoints/events/account_test.go index 8477b43b49b..d19a3912f59 100644 --- a/endpoints/events/account_test.go +++ b/endpoints/events/account_test.go @@ -9,10 +9,10 @@ import ( "testing" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/stored_requests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/endpoints/events/event.go b/endpoints/events/event.go index ffcb4e006e1..b92b72f17ad 100644 --- a/endpoints/events/event.go +++ b/endpoints/events/event.go @@ -4,22 +4,23 @@ import ( "context" "errors" "fmt" - "github.com/prebid/prebid-server/openrtb_ext" "net/http" "net/url" "strconv" "time" "unicode" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/julienschmidt/httprouter" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/httputil" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/httputil" ) const ( diff --git a/endpoints/events/event_test.go b/endpoints/events/event_test.go index 5fc115786fe..81d000fd8a4 100644 --- a/endpoints/events/event_test.go +++ b/endpoints/events/event_test.go @@ -12,12 +12,12 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/events/vtrack.go b/endpoints/events/vtrack.go index eeb409e24ae..5d794651ba4 100644 --- a/endpoints/events/vtrack.go +++ b/endpoints/events/vtrack.go @@ -11,15 +11,15 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/jsonutil" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) const ( diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index d950a443afe..3ccfeacf82f 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -12,11 +12,11 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/getuids.go b/endpoints/getuids.go index f420c64fa6b..ea87ce70568 100644 --- a/endpoints/getuids.go +++ b/endpoints/getuids.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/usersync" "encoding/json" ) diff --git a/endpoints/getuids_test.go b/endpoints/getuids_test.go index 7988acbaffe..c496d3e270b 100644 --- a/endpoints/getuids_test.go +++ b/endpoints/getuids_test.go @@ -5,7 +5,7 @@ import ( "net/http/httptest" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/info/bidders.go b/endpoints/info/bidders.go index bd6d078b3ba..7cbad5e26f6 100644 --- a/endpoints/info/bidders.go +++ b/endpoints/info/bidders.go @@ -7,8 +7,8 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) var invalidEnabledOnlyMsg = []byte(`Invalid value for 'enabledonly' query param, must be of boolean type`) diff --git a/endpoints/info/bidders_detail.go b/endpoints/info/bidders_detail.go index 34d14efde1b..fbc9ab43486 100644 --- a/endpoints/info/bidders_detail.go +++ b/endpoints/info/bidders_detail.go @@ -8,9 +8,9 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) const ( diff --git a/endpoints/info/bidders_detail_test.go b/endpoints/info/bidders_detail_test.go index 435d0cec92c..2911aa8e0e9 100644 --- a/endpoints/info/bidders_detail_test.go +++ b/endpoints/info/bidders_detail_test.go @@ -9,8 +9,8 @@ import ( "testing" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/info/bidders_test.go b/endpoints/info/bidders_test.go index 1f483e5de27..189eb865551 100644 --- a/endpoints/info/bidders_test.go +++ b/endpoints/info/bidders_test.go @@ -6,7 +6,7 @@ import ( "net/http/httptest" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 099f5d66c41..8f8d32c8fb9 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -16,29 +16,29 @@ import ( "github.com/julienschmidt/httprouter" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/ortb" - "github.com/prebid/prebid-server/util/uuidutil" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/ortb" + "github.com/prebid/prebid-server/v2/util/uuidutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/amp" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/version" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/amp" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/stored_responses" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/version" ) const defaultAmpRequestTimeoutMillis = 900 diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 7de8995754a..bd56457b3d7 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -18,21 +18,21 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/prebid/prebid-server/amp" - "github.com/prebid/prebid-server/analytics" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/amp" + "github.com/prebid/prebid-server/v2/analytics" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // TestGoodRequests makes sure that the auction runs properly-formatted stored bids correctly. diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index e6c9fe1fbde..72382f36b04 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -25,36 +25,36 @@ import ( nativeRequests "github.com/prebid/openrtb/v19/native1/request" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/bidadjustment" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/ortb" - "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/v2/bidadjustment" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/ortb" + "github.com/prebid/prebid-server/v2/privacy" "golang.org/x/net/publicsuffix" jsonpatch "gopkg.in/evanphx/json-patch.v4" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/lmt" - "github.com/prebid/prebid-server/schain" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/httputil" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/privacy/ccpa" + "github.com/prebid/prebid-server/v2/privacy/lmt" + "github.com/prebid/prebid-server/v2/schain" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/stored_responses" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/httputil" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/uuidutil" + "github.com/prebid/prebid-server/v2/version" ) const storedRequestTimeoutMillis = 50 diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index f3e82dca099..88a55e627e7 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -10,17 +10,17 @@ import ( "testing" "time" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/macros" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/usersync" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/macros" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/usersync" ) // benchmarkTestServer returns the header bidding test ad. This response was scraped from a real appnexus server response. diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index bab25261175..c521d653cac 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -22,22 +22,22 @@ import ( "github.com/prebid/openrtb/v19/native1" nativeRequests "github.com/prebid/openrtb/v19/native1/request" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/analytics" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/stored_responses" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/openrtb2/interstitial.go b/endpoints/openrtb2/interstitial.go index 46dc7a61510..330119b6f8f 100644 --- a/endpoints/openrtb2/interstitial.go +++ b/endpoints/openrtb2/interstitial.go @@ -4,9 +4,9 @@ import ( "fmt" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func processInterstitials(req *openrtb_ext.RequestWrapper) error { diff --git a/endpoints/openrtb2/interstitial_test.go b/endpoints/openrtb2/interstitial_test.go index 947817803b2..eb69bc91f08 100644 --- a/endpoints/openrtb2/interstitial_test.go +++ b/endpoints/openrtb2/interstitial_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index e8506813eef..cfc8f5d8dac 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -18,28 +18,28 @@ import ( "github.com/julienschmidt/httprouter" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/analytics" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - pbc "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/uuidutil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/analytics" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + pbc "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/uuidutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 94455e0cb52..05802bbd506 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -17,28 +17,28 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/ortb" - "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/ortb" + "github.com/prebid/prebid-server/v2/privacy" jsonpatch "gopkg.in/evanphx/json-patch.v4" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/uuidutil" + "github.com/prebid/prebid-server/v2/version" ) var defaultRequestTimeout int64 = 5000 diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 281c80b204d..70a37aab5df 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -11,20 +11,20 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/analytics" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/analytics" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" diff --git a/endpoints/setuid.go b/endpoints/setuid.go index 7af70d002b5..f9c55fc84de 100644 --- a/endpoints/setuid.go +++ b/endpoints/setuid.go @@ -12,19 +12,19 @@ import ( "github.com/julienschmidt/httprouter" gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - gppPrivacy "github.com/prebid/prebid-server/privacy/gpp" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/httputil" - stringutil "github.com/prebid/prebid-server/util/stringutil" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + gppPrivacy "github.com/prebid/prebid-server/v2/privacy/gpp" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/httputil" + stringutil "github.com/prebid/prebid-server/v2/util/stringutil" ) const ( diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 8b919c3d596..fe66379702c 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -12,18 +12,18 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/analytics" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/v2/analytics" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/usersync" "github.com/stretchr/testify/assert" - metricsConf "github.com/prebid/prebid-server/metrics/config" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" ) func TestSetUIDEndpoint(t *testing.T) { diff --git a/endpoints/version.go b/endpoints/version.go index 0a837fd8bc3..f9e07da9a0d 100644 --- a/endpoints/version.go +++ b/endpoints/version.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/golang/glog" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) const versionEndpointValueNotSet = "not-set" diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index fd1a99aeffb..a5179f75d53 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -1,194 +1,194 @@ package exchange import ( - "github.com/prebid/prebid-server/adapters" - ttx "github.com/prebid/prebid-server/adapters/33across" - "github.com/prebid/prebid-server/adapters/aax" - "github.com/prebid/prebid-server/adapters/aceex" - "github.com/prebid/prebid-server/adapters/acuityads" - "github.com/prebid/prebid-server/adapters/adf" - "github.com/prebid/prebid-server/adapters/adgeneration" - "github.com/prebid/prebid-server/adapters/adhese" - "github.com/prebid/prebid-server/adapters/adkernel" - "github.com/prebid/prebid-server/adapters/adkernelAdn" - "github.com/prebid/prebid-server/adapters/adman" - "github.com/prebid/prebid-server/adapters/admixer" - "github.com/prebid/prebid-server/adapters/adnuntius" - "github.com/prebid/prebid-server/adapters/adocean" - "github.com/prebid/prebid-server/adapters/adoppler" - "github.com/prebid/prebid-server/adapters/adot" - "github.com/prebid/prebid-server/adapters/adpone" - "github.com/prebid/prebid-server/adapters/adprime" - "github.com/prebid/prebid-server/adapters/adquery" - "github.com/prebid/prebid-server/adapters/adrino" - "github.com/prebid/prebid-server/adapters/adsinteractive" - "github.com/prebid/prebid-server/adapters/adtarget" - "github.com/prebid/prebid-server/adapters/adtelligent" - "github.com/prebid/prebid-server/adapters/adtrgtme" - "github.com/prebid/prebid-server/adapters/advangelists" - "github.com/prebid/prebid-server/adapters/adview" - "github.com/prebid/prebid-server/adapters/adxcg" - "github.com/prebid/prebid-server/adapters/adyoulike" - "github.com/prebid/prebid-server/adapters/aidem" - "github.com/prebid/prebid-server/adapters/aja" - "github.com/prebid/prebid-server/adapters/algorix" - "github.com/prebid/prebid-server/adapters/amx" - "github.com/prebid/prebid-server/adapters/apacdex" - "github.com/prebid/prebid-server/adapters/appnexus" - "github.com/prebid/prebid-server/adapters/appush" - "github.com/prebid/prebid-server/adapters/audienceNetwork" - "github.com/prebid/prebid-server/adapters/automatad" - "github.com/prebid/prebid-server/adapters/avocet" - "github.com/prebid/prebid-server/adapters/axis" - "github.com/prebid/prebid-server/adapters/axonix" - "github.com/prebid/prebid-server/adapters/beachfront" - "github.com/prebid/prebid-server/adapters/beintoo" - "github.com/prebid/prebid-server/adapters/bematterfull" - "github.com/prebid/prebid-server/adapters/between" - "github.com/prebid/prebid-server/adapters/beyondmedia" - "github.com/prebid/prebid-server/adapters/bidmachine" - "github.com/prebid/prebid-server/adapters/bidmyadz" - "github.com/prebid/prebid-server/adapters/bidscube" - "github.com/prebid/prebid-server/adapters/bidstack" - "github.com/prebid/prebid-server/adapters/bizzclick" - "github.com/prebid/prebid-server/adapters/bliink" - "github.com/prebid/prebid-server/adapters/blue" - "github.com/prebid/prebid-server/adapters/bluesea" - "github.com/prebid/prebid-server/adapters/bmtm" - "github.com/prebid/prebid-server/adapters/boldwin" - "github.com/prebid/prebid-server/adapters/brave" - cadentaperturemx "github.com/prebid/prebid-server/adapters/cadent_aperture_mx" - "github.com/prebid/prebid-server/adapters/ccx" - "github.com/prebid/prebid-server/adapters/coinzilla" - "github.com/prebid/prebid-server/adapters/colossus" - "github.com/prebid/prebid-server/adapters/compass" - "github.com/prebid/prebid-server/adapters/connectad" - "github.com/prebid/prebid-server/adapters/consumable" - "github.com/prebid/prebid-server/adapters/conversant" - "github.com/prebid/prebid-server/adapters/cpmstar" - "github.com/prebid/prebid-server/adapters/criteo" - "github.com/prebid/prebid-server/adapters/cwire" - "github.com/prebid/prebid-server/adapters/datablocks" - "github.com/prebid/prebid-server/adapters/decenterads" - "github.com/prebid/prebid-server/adapters/deepintent" - "github.com/prebid/prebid-server/adapters/definemedia" - "github.com/prebid/prebid-server/adapters/dianomi" - "github.com/prebid/prebid-server/adapters/dmx" - "github.com/prebid/prebid-server/adapters/dxkulture" - evolution "github.com/prebid/prebid-server/adapters/e_volution" - "github.com/prebid/prebid-server/adapters/edge226" - "github.com/prebid/prebid-server/adapters/emtv" - "github.com/prebid/prebid-server/adapters/eplanning" - "github.com/prebid/prebid-server/adapters/epom" - "github.com/prebid/prebid-server/adapters/flipp" - "github.com/prebid/prebid-server/adapters/freewheelssp" - "github.com/prebid/prebid-server/adapters/frvradn" - "github.com/prebid/prebid-server/adapters/gamma" - "github.com/prebid/prebid-server/adapters/gamoshi" - "github.com/prebid/prebid-server/adapters/globalsun" - "github.com/prebid/prebid-server/adapters/gothamads" - "github.com/prebid/prebid-server/adapters/grid" - "github.com/prebid/prebid-server/adapters/gumgum" - "github.com/prebid/prebid-server/adapters/huaweiads" - "github.com/prebid/prebid-server/adapters/imds" - "github.com/prebid/prebid-server/adapters/impactify" - "github.com/prebid/prebid-server/adapters/improvedigital" - "github.com/prebid/prebid-server/adapters/infytv" - "github.com/prebid/prebid-server/adapters/inmobi" - "github.com/prebid/prebid-server/adapters/interactiveoffers" - "github.com/prebid/prebid-server/adapters/invibes" - "github.com/prebid/prebid-server/adapters/iqx" - "github.com/prebid/prebid-server/adapters/iqzone" - "github.com/prebid/prebid-server/adapters/ix" - "github.com/prebid/prebid-server/adapters/jixie" - "github.com/prebid/prebid-server/adapters/kargo" - "github.com/prebid/prebid-server/adapters/kayzen" - "github.com/prebid/prebid-server/adapters/kidoz" - "github.com/prebid/prebid-server/adapters/kiviads" - "github.com/prebid/prebid-server/adapters/krushmedia" - "github.com/prebid/prebid-server/adapters/lemmadigital" - "github.com/prebid/prebid-server/adapters/liftoff" - "github.com/prebid/prebid-server/adapters/limelightDigital" - lmkiviads "github.com/prebid/prebid-server/adapters/lm_kiviads" - "github.com/prebid/prebid-server/adapters/lockerdome" - "github.com/prebid/prebid-server/adapters/logan" - "github.com/prebid/prebid-server/adapters/logicad" - "github.com/prebid/prebid-server/adapters/lunamedia" - mabidder "github.com/prebid/prebid-server/adapters/mabidder" - "github.com/prebid/prebid-server/adapters/madvertise" - "github.com/prebid/prebid-server/adapters/marsmedia" - "github.com/prebid/prebid-server/adapters/medianet" - "github.com/prebid/prebid-server/adapters/mgid" - "github.com/prebid/prebid-server/adapters/mgidX" - "github.com/prebid/prebid-server/adapters/mobfoxpb" - "github.com/prebid/prebid-server/adapters/mobilefuse" - "github.com/prebid/prebid-server/adapters/motorik" - "github.com/prebid/prebid-server/adapters/nextmillennium" - "github.com/prebid/prebid-server/adapters/nobid" - "github.com/prebid/prebid-server/adapters/onetag" - "github.com/prebid/prebid-server/adapters/openweb" - "github.com/prebid/prebid-server/adapters/openx" - "github.com/prebid/prebid-server/adapters/operaads" - "github.com/prebid/prebid-server/adapters/orbidder" - "github.com/prebid/prebid-server/adapters/outbrain" - "github.com/prebid/prebid-server/adapters/ownadx" - "github.com/prebid/prebid-server/adapters/pangle" - "github.com/prebid/prebid-server/adapters/pgamssp" - "github.com/prebid/prebid-server/adapters/pubmatic" - "github.com/prebid/prebid-server/adapters/pubnative" - "github.com/prebid/prebid-server/adapters/pulsepoint" - "github.com/prebid/prebid-server/adapters/pwbid" - "github.com/prebid/prebid-server/adapters/revcontent" - "github.com/prebid/prebid-server/adapters/richaudience" - "github.com/prebid/prebid-server/adapters/rise" - "github.com/prebid/prebid-server/adapters/rtbhouse" - "github.com/prebid/prebid-server/adapters/rubicon" - salunamedia "github.com/prebid/prebid-server/adapters/sa_lunamedia" - "github.com/prebid/prebid-server/adapters/screencore" - "github.com/prebid/prebid-server/adapters/seedingAlliance" - "github.com/prebid/prebid-server/adapters/sharethrough" - "github.com/prebid/prebid-server/adapters/silvermob" - "github.com/prebid/prebid-server/adapters/silverpush" - "github.com/prebid/prebid-server/adapters/smaato" - "github.com/prebid/prebid-server/adapters/smartadserver" - "github.com/prebid/prebid-server/adapters/smarthub" - "github.com/prebid/prebid-server/adapters/smartrtb" - "github.com/prebid/prebid-server/adapters/smartx" - "github.com/prebid/prebid-server/adapters/smartyads" - "github.com/prebid/prebid-server/adapters/smilewanted" - "github.com/prebid/prebid-server/adapters/sonobi" - "github.com/prebid/prebid-server/adapters/sovrn" - "github.com/prebid/prebid-server/adapters/sspBC" - "github.com/prebid/prebid-server/adapters/stroeerCore" - "github.com/prebid/prebid-server/adapters/suntContent" - "github.com/prebid/prebid-server/adapters/taboola" - "github.com/prebid/prebid-server/adapters/tappx" - "github.com/prebid/prebid-server/adapters/teads" - "github.com/prebid/prebid-server/adapters/telaria" - "github.com/prebid/prebid-server/adapters/tpmn" - "github.com/prebid/prebid-server/adapters/trafficgate" - "github.com/prebid/prebid-server/adapters/triplelift" - "github.com/prebid/prebid-server/adapters/triplelift_native" - "github.com/prebid/prebid-server/adapters/ucfunnel" - "github.com/prebid/prebid-server/adapters/undertone" - "github.com/prebid/prebid-server/adapters/unicorn" - "github.com/prebid/prebid-server/adapters/unruly" - "github.com/prebid/prebid-server/adapters/videobyte" - "github.com/prebid/prebid-server/adapters/videoheroes" - "github.com/prebid/prebid-server/adapters/vidoomy" - "github.com/prebid/prebid-server/adapters/visiblemeasures" - "github.com/prebid/prebid-server/adapters/visx" - "github.com/prebid/prebid-server/adapters/vox" - "github.com/prebid/prebid-server/adapters/vrtcal" - "github.com/prebid/prebid-server/adapters/xeworks" - "github.com/prebid/prebid-server/adapters/yahooAds" - "github.com/prebid/prebid-server/adapters/yeahmobi" - "github.com/prebid/prebid-server/adapters/yieldlab" - "github.com/prebid/prebid-server/adapters/yieldmo" - "github.com/prebid/prebid-server/adapters/yieldone" - "github.com/prebid/prebid-server/adapters/zeroclickfraud" - "github.com/prebid/prebid-server/adapters/zeta_global_ssp" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + ttx "github.com/prebid/prebid-server/v2/adapters/33across" + "github.com/prebid/prebid-server/v2/adapters/aax" + "github.com/prebid/prebid-server/v2/adapters/aceex" + "github.com/prebid/prebid-server/v2/adapters/acuityads" + "github.com/prebid/prebid-server/v2/adapters/adf" + "github.com/prebid/prebid-server/v2/adapters/adgeneration" + "github.com/prebid/prebid-server/v2/adapters/adhese" + "github.com/prebid/prebid-server/v2/adapters/adkernel" + "github.com/prebid/prebid-server/v2/adapters/adkernelAdn" + "github.com/prebid/prebid-server/v2/adapters/adman" + "github.com/prebid/prebid-server/v2/adapters/admixer" + "github.com/prebid/prebid-server/v2/adapters/adnuntius" + "github.com/prebid/prebid-server/v2/adapters/adocean" + "github.com/prebid/prebid-server/v2/adapters/adoppler" + "github.com/prebid/prebid-server/v2/adapters/adot" + "github.com/prebid/prebid-server/v2/adapters/adpone" + "github.com/prebid/prebid-server/v2/adapters/adprime" + "github.com/prebid/prebid-server/v2/adapters/adquery" + "github.com/prebid/prebid-server/v2/adapters/adrino" + "github.com/prebid/prebid-server/v2/adapters/adsinteractive" + "github.com/prebid/prebid-server/v2/adapters/adtarget" + "github.com/prebid/prebid-server/v2/adapters/adtelligent" + "github.com/prebid/prebid-server/v2/adapters/adtrgtme" + "github.com/prebid/prebid-server/v2/adapters/advangelists" + "github.com/prebid/prebid-server/v2/adapters/adview" + "github.com/prebid/prebid-server/v2/adapters/adxcg" + "github.com/prebid/prebid-server/v2/adapters/adyoulike" + "github.com/prebid/prebid-server/v2/adapters/aidem" + "github.com/prebid/prebid-server/v2/adapters/aja" + "github.com/prebid/prebid-server/v2/adapters/algorix" + "github.com/prebid/prebid-server/v2/adapters/amx" + "github.com/prebid/prebid-server/v2/adapters/apacdex" + "github.com/prebid/prebid-server/v2/adapters/appnexus" + "github.com/prebid/prebid-server/v2/adapters/appush" + "github.com/prebid/prebid-server/v2/adapters/audienceNetwork" + "github.com/prebid/prebid-server/v2/adapters/automatad" + "github.com/prebid/prebid-server/v2/adapters/avocet" + "github.com/prebid/prebid-server/v2/adapters/axis" + "github.com/prebid/prebid-server/v2/adapters/axonix" + "github.com/prebid/prebid-server/v2/adapters/beachfront" + "github.com/prebid/prebid-server/v2/adapters/beintoo" + "github.com/prebid/prebid-server/v2/adapters/bematterfull" + "github.com/prebid/prebid-server/v2/adapters/between" + "github.com/prebid/prebid-server/v2/adapters/beyondmedia" + "github.com/prebid/prebid-server/v2/adapters/bidmachine" + "github.com/prebid/prebid-server/v2/adapters/bidmyadz" + "github.com/prebid/prebid-server/v2/adapters/bidscube" + "github.com/prebid/prebid-server/v2/adapters/bidstack" + "github.com/prebid/prebid-server/v2/adapters/bizzclick" + "github.com/prebid/prebid-server/v2/adapters/bliink" + "github.com/prebid/prebid-server/v2/adapters/blue" + "github.com/prebid/prebid-server/v2/adapters/bluesea" + "github.com/prebid/prebid-server/v2/adapters/bmtm" + "github.com/prebid/prebid-server/v2/adapters/boldwin" + "github.com/prebid/prebid-server/v2/adapters/brave" + cadentaperturemx "github.com/prebid/prebid-server/v2/adapters/cadent_aperture_mx" + "github.com/prebid/prebid-server/v2/adapters/ccx" + "github.com/prebid/prebid-server/v2/adapters/coinzilla" + "github.com/prebid/prebid-server/v2/adapters/colossus" + "github.com/prebid/prebid-server/v2/adapters/compass" + "github.com/prebid/prebid-server/v2/adapters/connectad" + "github.com/prebid/prebid-server/v2/adapters/consumable" + "github.com/prebid/prebid-server/v2/adapters/conversant" + "github.com/prebid/prebid-server/v2/adapters/cpmstar" + "github.com/prebid/prebid-server/v2/adapters/criteo" + "github.com/prebid/prebid-server/v2/adapters/cwire" + "github.com/prebid/prebid-server/v2/adapters/datablocks" + "github.com/prebid/prebid-server/v2/adapters/decenterads" + "github.com/prebid/prebid-server/v2/adapters/deepintent" + "github.com/prebid/prebid-server/v2/adapters/definemedia" + "github.com/prebid/prebid-server/v2/adapters/dianomi" + "github.com/prebid/prebid-server/v2/adapters/dmx" + "github.com/prebid/prebid-server/v2/adapters/dxkulture" + evolution "github.com/prebid/prebid-server/v2/adapters/e_volution" + "github.com/prebid/prebid-server/v2/adapters/edge226" + "github.com/prebid/prebid-server/v2/adapters/emtv" + "github.com/prebid/prebid-server/v2/adapters/eplanning" + "github.com/prebid/prebid-server/v2/adapters/epom" + "github.com/prebid/prebid-server/v2/adapters/flipp" + "github.com/prebid/prebid-server/v2/adapters/freewheelssp" + "github.com/prebid/prebid-server/v2/adapters/frvradn" + "github.com/prebid/prebid-server/v2/adapters/gamma" + "github.com/prebid/prebid-server/v2/adapters/gamoshi" + "github.com/prebid/prebid-server/v2/adapters/globalsun" + "github.com/prebid/prebid-server/v2/adapters/gothamads" + "github.com/prebid/prebid-server/v2/adapters/grid" + "github.com/prebid/prebid-server/v2/adapters/gumgum" + "github.com/prebid/prebid-server/v2/adapters/huaweiads" + "github.com/prebid/prebid-server/v2/adapters/imds" + "github.com/prebid/prebid-server/v2/adapters/impactify" + "github.com/prebid/prebid-server/v2/adapters/improvedigital" + "github.com/prebid/prebid-server/v2/adapters/infytv" + "github.com/prebid/prebid-server/v2/adapters/inmobi" + "github.com/prebid/prebid-server/v2/adapters/interactiveoffers" + "github.com/prebid/prebid-server/v2/adapters/invibes" + "github.com/prebid/prebid-server/v2/adapters/iqx" + "github.com/prebid/prebid-server/v2/adapters/iqzone" + "github.com/prebid/prebid-server/v2/adapters/ix" + "github.com/prebid/prebid-server/v2/adapters/jixie" + "github.com/prebid/prebid-server/v2/adapters/kargo" + "github.com/prebid/prebid-server/v2/adapters/kayzen" + "github.com/prebid/prebid-server/v2/adapters/kidoz" + "github.com/prebid/prebid-server/v2/adapters/kiviads" + "github.com/prebid/prebid-server/v2/adapters/krushmedia" + "github.com/prebid/prebid-server/v2/adapters/lemmadigital" + "github.com/prebid/prebid-server/v2/adapters/liftoff" + "github.com/prebid/prebid-server/v2/adapters/limelightDigital" + lmkiviads "github.com/prebid/prebid-server/v2/adapters/lm_kiviads" + "github.com/prebid/prebid-server/v2/adapters/lockerdome" + "github.com/prebid/prebid-server/v2/adapters/logan" + "github.com/prebid/prebid-server/v2/adapters/logicad" + "github.com/prebid/prebid-server/v2/adapters/lunamedia" + mabidder "github.com/prebid/prebid-server/v2/adapters/mabidder" + "github.com/prebid/prebid-server/v2/adapters/madvertise" + "github.com/prebid/prebid-server/v2/adapters/marsmedia" + "github.com/prebid/prebid-server/v2/adapters/medianet" + "github.com/prebid/prebid-server/v2/adapters/mgid" + "github.com/prebid/prebid-server/v2/adapters/mgidX" + "github.com/prebid/prebid-server/v2/adapters/mobfoxpb" + "github.com/prebid/prebid-server/v2/adapters/mobilefuse" + "github.com/prebid/prebid-server/v2/adapters/motorik" + "github.com/prebid/prebid-server/v2/adapters/nextmillennium" + "github.com/prebid/prebid-server/v2/adapters/nobid" + "github.com/prebid/prebid-server/v2/adapters/onetag" + "github.com/prebid/prebid-server/v2/adapters/openweb" + "github.com/prebid/prebid-server/v2/adapters/openx" + "github.com/prebid/prebid-server/v2/adapters/operaads" + "github.com/prebid/prebid-server/v2/adapters/orbidder" + "github.com/prebid/prebid-server/v2/adapters/outbrain" + "github.com/prebid/prebid-server/v2/adapters/ownadx" + "github.com/prebid/prebid-server/v2/adapters/pangle" + "github.com/prebid/prebid-server/v2/adapters/pgamssp" + "github.com/prebid/prebid-server/v2/adapters/pubmatic" + "github.com/prebid/prebid-server/v2/adapters/pubnative" + "github.com/prebid/prebid-server/v2/adapters/pulsepoint" + "github.com/prebid/prebid-server/v2/adapters/pwbid" + "github.com/prebid/prebid-server/v2/adapters/revcontent" + "github.com/prebid/prebid-server/v2/adapters/richaudience" + "github.com/prebid/prebid-server/v2/adapters/rise" + "github.com/prebid/prebid-server/v2/adapters/rtbhouse" + "github.com/prebid/prebid-server/v2/adapters/rubicon" + salunamedia "github.com/prebid/prebid-server/v2/adapters/sa_lunamedia" + "github.com/prebid/prebid-server/v2/adapters/screencore" + "github.com/prebid/prebid-server/v2/adapters/seedingAlliance" + "github.com/prebid/prebid-server/v2/adapters/sharethrough" + "github.com/prebid/prebid-server/v2/adapters/silvermob" + "github.com/prebid/prebid-server/v2/adapters/silverpush" + "github.com/prebid/prebid-server/v2/adapters/smaato" + "github.com/prebid/prebid-server/v2/adapters/smartadserver" + "github.com/prebid/prebid-server/v2/adapters/smarthub" + "github.com/prebid/prebid-server/v2/adapters/smartrtb" + "github.com/prebid/prebid-server/v2/adapters/smartx" + "github.com/prebid/prebid-server/v2/adapters/smartyads" + "github.com/prebid/prebid-server/v2/adapters/smilewanted" + "github.com/prebid/prebid-server/v2/adapters/sonobi" + "github.com/prebid/prebid-server/v2/adapters/sovrn" + "github.com/prebid/prebid-server/v2/adapters/sspBC" + "github.com/prebid/prebid-server/v2/adapters/stroeerCore" + "github.com/prebid/prebid-server/v2/adapters/suntContent" + "github.com/prebid/prebid-server/v2/adapters/taboola" + "github.com/prebid/prebid-server/v2/adapters/tappx" + "github.com/prebid/prebid-server/v2/adapters/teads" + "github.com/prebid/prebid-server/v2/adapters/telaria" + "github.com/prebid/prebid-server/v2/adapters/tpmn" + "github.com/prebid/prebid-server/v2/adapters/trafficgate" + "github.com/prebid/prebid-server/v2/adapters/triplelift" + "github.com/prebid/prebid-server/v2/adapters/triplelift_native" + "github.com/prebid/prebid-server/v2/adapters/ucfunnel" + "github.com/prebid/prebid-server/v2/adapters/undertone" + "github.com/prebid/prebid-server/v2/adapters/unicorn" + "github.com/prebid/prebid-server/v2/adapters/unruly" + "github.com/prebid/prebid-server/v2/adapters/videobyte" + "github.com/prebid/prebid-server/v2/adapters/videoheroes" + "github.com/prebid/prebid-server/v2/adapters/vidoomy" + "github.com/prebid/prebid-server/v2/adapters/visiblemeasures" + "github.com/prebid/prebid-server/v2/adapters/visx" + "github.com/prebid/prebid-server/v2/adapters/vox" + "github.com/prebid/prebid-server/v2/adapters/vrtcal" + "github.com/prebid/prebid-server/v2/adapters/xeworks" + "github.com/prebid/prebid-server/v2/adapters/yahooAds" + "github.com/prebid/prebid-server/v2/adapters/yeahmobi" + "github.com/prebid/prebid-server/v2/adapters/yieldlab" + "github.com/prebid/prebid-server/v2/adapters/yieldmo" + "github.com/prebid/prebid-server/v2/adapters/yieldone" + "github.com/prebid/prebid-server/v2/adapters/zeroclickfraud" + "github.com/prebid/prebid-server/v2/adapters/zeta_global_ssp" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Adapter registration is kept in this separate file for ease of use and to aid diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 89d58d06800..cd70530bfc3 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -4,10 +4,10 @@ import ( "fmt" "net/http" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config.BidderInfos, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]AdaptedBidder, []error) { diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index 08d751cdadf..8f765305248 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/appnexus" - "github.com/prebid/prebid-server/adapters/rubicon" - "github.com/prebid/prebid-server/config" - metrics "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/appnexus" + "github.com/prebid/prebid-server/v2/adapters/rubicon" + "github.com/prebid/prebid-server/v2/config" + metrics "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/exchange/auction.go b/exchange/auction.go index abbf56ba799..17a843fb5ed 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -12,11 +12,11 @@ import ( uuid "github.com/gofrs/uuid" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) const ( diff --git a/exchange/auction_response.go b/exchange/auction_response.go index 3b85a4472c2..c92798d0f3b 100644 --- a/exchange/auction_response.go +++ b/exchange/auction_response.go @@ -2,7 +2,7 @@ package exchange import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // AuctionResponse contains OpenRTB Bid Response object and its extension (un-marshalled) object diff --git a/exchange/auction_test.go b/exchange/auction_test.go index 199b2abba77..10c4b9a5e67 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -12,12 +12,12 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/exchange/bidder.go b/exchange/bidder.go index 677743801f3..db654bf47ae 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -16,24 +16,24 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/bidadjustment" - "github.com/prebid/prebid-server/config/util" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v2/bidadjustment" + "github.com/prebid/prebid-server/v2/config/util" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/version" "github.com/prebid/openrtb/v19/adcom1" nativeRequests "github.com/prebid/openrtb/v19/native1/request" nativeResponse "github.com/prebid/openrtb/v19/native1/response" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "golang.org/x/net/context/ctxhttp" ) diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index b7203e33b17..824aecc8583 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -21,18 +21,18 @@ import ( nativeRequests "github.com/prebid/openrtb/v19/native1/request" nativeResponse "github.com/prebid/openrtb/v19/native1/response" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/version" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index 9b5771a3497..651b2c0f420 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -7,12 +7,12 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/openrtb_ext" goCurrency "golang.org/x/text/currency" ) diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index ed6173b64ad..a495556f424 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -5,12 +5,12 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/exchange/entities/entities.go b/exchange/entities/entities.go index 1220da5c812..f106003d8ca 100644 --- a/exchange/entities/entities.go +++ b/exchange/entities/entities.go @@ -2,7 +2,7 @@ package entities import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // PbsOrtbSeatBid is a SeatBid returned by an AdaptedBidder. diff --git a/exchange/events.go b/exchange/events.go index fb535f6f70e..b20eea328f6 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -3,14 +3,14 @@ package exchange import ( "time" - "github.com/prebid/prebid-server/exchange/entities" + "github.com/prebid/prebid-server/v2/exchange/entities" jsonpatch "gopkg.in/evanphx/json-patch.v4" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/endpoints/events" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/endpoints/events" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // eventTracking has configuration fields needed for adding event tracking to an auction response diff --git a/exchange/events_test.go b/exchange/events_test.go index 24dedf1a6f1..3d191f5f63c 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/exchange/exchange.go b/exchange/exchange.go index 6ebde7dca9d..b07c4bdf2a5 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -14,29 +14,29 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/privacy" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adservertargeting" - "github.com/prebid/prebid-server/bidadjustment" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/firstpartydata" - "github.com/prebid/prebid-server/floors" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/maputil" + "github.com/prebid/prebid-server/v2/privacy" + + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adservertargeting" + "github.com/prebid/prebid-server/v2/bidadjustment" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/firstpartydata" + "github.com/prebid/prebid-server/v2/floors" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_responses" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/maputil" "github.com/buger/jsonparser" "github.com/gofrs/uuid" diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index ff989bad91d..bfeb9374af3 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -21,28 +21,28 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - metricsConf "github.com/prebid/prebid-server/metrics/config" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - pbc "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + pbc "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/file_fetcher" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" jsonpatch "gopkg.in/evanphx/json-patch.v4" diff --git a/exchange/gdpr.go b/exchange/gdpr.go index d503eb5da27..52fb860f5df 100644 --- a/exchange/gdpr.go +++ b/exchange/gdpr.go @@ -3,9 +3,9 @@ package exchange import ( gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/openrtb_ext" - gppPolicy "github.com/prebid/prebid-server/privacy/gpp" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/openrtb_ext" + gppPolicy "github.com/prebid/prebid-server/v2/privacy/gpp" ) // getGDPR will pull the gdpr flag from an openrtb request diff --git a/exchange/gdpr_test.go b/exchange/gdpr_test.go index 44573b59167..0e12ec66568 100644 --- a/exchange/gdpr_test.go +++ b/exchange/gdpr_test.go @@ -7,8 +7,8 @@ import ( gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/exchange/price_granularity.go b/exchange/price_granularity.go index af9e46b20fe..ee3104605d7 100644 --- a/exchange/price_granularity.go +++ b/exchange/price_granularity.go @@ -1,10 +1,11 @@ package exchange import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" "math" "strconv" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // GetPriceBucket is the externally facing function for computing CPM buckets diff --git a/exchange/price_granularity_test.go b/exchange/price_granularity_test.go index 4f9337aadc3..810dbcdf45a 100644 --- a/exchange/price_granularity_test.go +++ b/exchange/price_granularity_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/exchange/seat_non_bids.go b/exchange/seat_non_bids.go index 463a4595c85..78c1b23e3f3 100644 --- a/exchange/seat_non_bids.go +++ b/exchange/seat_non_bids.go @@ -1,8 +1,8 @@ package exchange import ( - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type nonBids struct { diff --git a/exchange/seat_non_bids_test.go b/exchange/seat_non_bids_test.go index d9f7aa88ca0..1a6b488b542 100644 --- a/exchange/seat_non_bids_test.go +++ b/exchange/seat_non_bids_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/exchange/targeting.go b/exchange/targeting.go index dbbf10041c9..d278c2f5873 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -5,7 +5,7 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const MaxKeyLength = 20 diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 25c6ea1714a..8742a4f5d2a 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -8,16 +8,16 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks/hookexecution" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/assert" diff --git a/exchange/tmax_adjustments.go b/exchange/tmax_adjustments.go index 29e732995af..55e2b18ad01 100644 --- a/exchange/tmax_adjustments.go +++ b/exchange/tmax_adjustments.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) type TmaxAdjustmentsPreprocessed struct { diff --git a/exchange/tmax_adjustments_test.go b/exchange/tmax_adjustments_test.go index 7e6a02ab81e..ce6f1736adf 100644 --- a/exchange/tmax_adjustments_test.go +++ b/exchange/tmax_adjustments_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" ) diff --git a/exchange/utils.go b/exchange/utils.go index 8b4774a36f4..150fac3ca53 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -15,19 +15,19 @@ import ( gppConstants "github.com/prebid/go-gpp/constants" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/firstpartydata" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/lmt" - "github.com/prebid/prebid-server/schain" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/firstpartydata" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/privacy/ccpa" + "github.com/prebid/prebid-server/v2/privacy/lmt" + "github.com/prebid/prebid-server/v2/schain" + "github.com/prebid/prebid-server/v2/stored_responses" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" ) var channelTypeMap = map[metrics.RequestType]config.ChannelType{ diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 3a00c37c7b1..03184b7402d 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -8,20 +8,20 @@ import ( "sort" "testing" - "github.com/prebid/prebid-server/stored_responses" + "github.com/prebid/prebid-server/v2/stored_responses" gpplib "github.com/prebid/go-gpp" "github.com/prebid/go-gpp/constants" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/firstpartydata" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/firstpartydata" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/experiment/adscert/inprocesssigner.go b/experiment/adscert/inprocesssigner.go index 604287f9ed6..eabd35ebb95 100644 --- a/experiment/adscert/inprocesssigner.go +++ b/experiment/adscert/inprocesssigner.go @@ -2,12 +2,13 @@ package adscert import ( "crypto/rand" + "time" + "github.com/IABTechLab/adscert/pkg/adscert/api" "github.com/IABTechLab/adscert/pkg/adscert/discovery" "github.com/IABTechLab/adscert/pkg/adscert/signatory" "github.com/benbjohnson/clock" - "github.com/prebid/prebid-server/config" - "time" + "github.com/prebid/prebid-server/v2/config" ) // inProcessSigner holds the signatory to add adsCert header to requests using in process go library diff --git a/experiment/adscert/remotesigner.go b/experiment/adscert/remotesigner.go index 3c9479560b2..d23dad201d3 100644 --- a/experiment/adscert/remotesigner.go +++ b/experiment/adscert/remotesigner.go @@ -2,12 +2,13 @@ package adscert import ( "fmt" + "time" + "github.com/IABTechLab/adscert/pkg/adscert/api" "github.com/IABTechLab/adscert/pkg/adscert/signatory" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "time" ) // remoteSigner holds the signatory to add adsCert header to requests using remote signing server diff --git a/experiment/adscert/signer.go b/experiment/adscert/signer.go index 08b3f655fa2..f060f957149 100644 --- a/experiment/adscert/signer.go +++ b/experiment/adscert/signer.go @@ -2,10 +2,11 @@ package adscert import ( "fmt" + "github.com/IABTechLab/adscert/pkg/adscert/api" "github.com/IABTechLab/adscert/pkg/adscert/logger" "github.com/IABTechLab/adscert/pkg/adscert/signatory" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) const SignHeader = "X-Ads-Cert-Auth" diff --git a/experiment/adscert/signer_test.go b/experiment/adscert/signer_test.go index d6d02175d95..fceb2e5c79c 100644 --- a/experiment/adscert/signer_test.go +++ b/experiment/adscert/signer_test.go @@ -2,10 +2,11 @@ package adscert import ( "errors" + "testing" + "github.com/IABTechLab/adscert/pkg/adscert/api" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" - "testing" ) func TestNilSigner(t *testing.T) { diff --git a/firstpartydata/extmerger.go b/firstpartydata/extmerger.go index 119fa8a4c3c..f3196bea996 100644 --- a/firstpartydata/extmerger.go +++ b/firstpartydata/extmerger.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) diff --git a/firstpartydata/extmerger_test.go b/firstpartydata/extmerger_test.go index 784163ac313..4107b0d1144 100644 --- a/firstpartydata/extmerger_test.go +++ b/firstpartydata/extmerger_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" "github.com/stretchr/testify/assert" ) diff --git a/firstpartydata/first_party_data.go b/firstpartydata/first_party_data.go index b23ba576fcf..8e482ce700b 100644 --- a/firstpartydata/first_party_data.go +++ b/firstpartydata/first_party_data.go @@ -7,11 +7,11 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" jsonpatch "gopkg.in/evanphx/json-patch.v4" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/ortb" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/ortb" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" ) const ( diff --git a/firstpartydata/first_party_data_test.go b/firstpartydata/first_party_data_test.go index 61369738bf3..aa00c981fa7 100644 --- a/firstpartydata/first_party_data_test.go +++ b/firstpartydata/first_party_data_test.go @@ -8,9 +8,9 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/floors/enforce.go b/floors/enforce.go index 9318c9d278e..e1f4d8d015b 100644 --- a/floors/enforce.go +++ b/floors/enforce.go @@ -6,10 +6,10 @@ import ( "math/rand" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Enforce does floors enforcement for bids from all bidders based on floors provided in request, account level floors config diff --git a/floors/enforce_test.go b/floors/enforce_test.go index 725ac22b193..085506c3411 100644 --- a/floors/enforce_test.go +++ b/floors/enforce_test.go @@ -7,11 +7,11 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/floors/floors.go b/floors/floors.go index 52591915058..c1e930298ac 100644 --- a/floors/floors.go +++ b/floors/floors.go @@ -5,9 +5,9 @@ import ( "math" "math/rand" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type Price struct { diff --git a/floors/floors_test.go b/floors/floors_test.go index b3002c2f155..56b21a1a209 100644 --- a/floors/floors_test.go +++ b/floors/floors_test.go @@ -6,10 +6,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/floors/rule.go b/floors/rule.go index f5f74cb6acf..db12719c337 100644 --- a/floors/rule.go +++ b/floors/rule.go @@ -9,8 +9,8 @@ import ( "github.com/golang/glog" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/floors/rule_test.go b/floors/rule_test.go index 7c484a7c95f..1e75956243d 100644 --- a/floors/rule_test.go +++ b/floors/rule_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/floors/validate.go b/floors/validate.go index 5624735c852..5dd843b13e0 100644 --- a/floors/validate.go +++ b/floors/validate.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validSchemaDimensions = map[string]struct{}{ diff --git a/floors/validate_test.go b/floors/validate_test.go index 96dad819e06..59d08afc5c0 100644 --- a/floors/validate_test.go +++ b/floors/validate_test.go @@ -5,8 +5,8 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/aggregated_config.go b/gdpr/aggregated_config.go index bbfb503225d..4bd1533de0f 100644 --- a/gdpr/aggregated_config.go +++ b/gdpr/aggregated_config.go @@ -3,8 +3,8 @@ package gdpr import ( "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TCF2ConfigReader is an interface to access TCF2 configurations diff --git a/gdpr/aggregated_config_test.go b/gdpr/aggregated_config_test.go index bf2d3bbb8f8..54d1c901853 100644 --- a/gdpr/aggregated_config_test.go +++ b/gdpr/aggregated_config_test.go @@ -5,8 +5,8 @@ import ( "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/basic_enforcement.go b/gdpr/basic_enforcement.go index f4559c4643d..322bb30986f 100644 --- a/gdpr/basic_enforcement.go +++ b/gdpr/basic_enforcement.go @@ -2,7 +2,7 @@ package gdpr import ( tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // BasicEnforcement determines if legal basis is satisfied for a given purpose and bidder using diff --git a/gdpr/basic_enforcement_test.go b/gdpr/basic_enforcement_test.go index c49e59ea595..06472618a83 100644 --- a/gdpr/basic_enforcement_test.go +++ b/gdpr/basic_enforcement_test.go @@ -6,7 +6,7 @@ import ( "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorconsent" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/full_enforcement.go b/gdpr/full_enforcement.go index eefa28d5499..c872e13e454 100644 --- a/gdpr/full_enforcement.go +++ b/gdpr/full_enforcement.go @@ -2,7 +2,7 @@ package gdpr import ( tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/gdpr/full_enforcement_test.go b/gdpr/full_enforcement_test.go index 4a859ecaabb..dac9d7ef76a 100644 --- a/gdpr/full_enforcement_test.go +++ b/gdpr/full_enforcement_test.go @@ -8,8 +8,8 @@ import ( tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index db6aa125383..1b4f6cb4680 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -3,8 +3,8 @@ package gdpr import ( "context" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type Permissions interface { diff --git a/gdpr/gdpr_test.go b/gdpr/gdpr_test.go index 1e56c5fdede..9604e24f4f0 100644 --- a/gdpr/gdpr_test.go +++ b/gdpr/gdpr_test.go @@ -6,8 +6,8 @@ import ( "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorlist" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/impl.go b/gdpr/impl.go index cc88a1fd3c6..fd3ad2b2dd9 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -6,7 +6,7 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const noBidder openrtb_ext.BidderName = "" diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 835a580f6e2..fc3d69d9c57 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -9,8 +9,8 @@ import ( "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/purpose_config.go b/gdpr/purpose_config.go index 015f23269ef..09edef94384 100644 --- a/gdpr/purpose_config.go +++ b/gdpr/purpose_config.go @@ -2,8 +2,8 @@ package gdpr import ( "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // purposeConfig represents all of the config info selected from the host and account configs for diff --git a/gdpr/purpose_config_test.go b/gdpr/purpose_config_test.go index e80733cc8ca..4837b62a2aa 100644 --- a/gdpr/purpose_config_test.go +++ b/gdpr/purpose_config_test.go @@ -3,7 +3,7 @@ package gdpr import ( "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/purpose_enforcer.go b/gdpr/purpose_enforcer.go index c8e76f988aa..4a138e76cf8 100644 --- a/gdpr/purpose_enforcer.go +++ b/gdpr/purpose_enforcer.go @@ -4,8 +4,8 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // PurposeEnforcer represents the enforcement strategy for determining if legal basis is achieved for a purpose diff --git a/gdpr/purpose_enforcer_test.go b/gdpr/purpose_enforcer_test.go index ea2075d9c65..ed1176b12f3 100644 --- a/gdpr/purpose_enforcer_test.go +++ b/gdpr/purpose_enforcer_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/signal.go b/gdpr/signal.go index ed7fe1dd8ea..3d2e4de1251 100644 --- a/gdpr/signal.go +++ b/gdpr/signal.go @@ -3,7 +3,7 @@ package gdpr import ( "strconv" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" ) type Signal int diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index da6bdbae415..64424f5ee69 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -14,7 +14,7 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "golang.org/x/net/context/ctxhttp" ) diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index b8c73bbf9a4..98dc4ba5aa3 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -11,8 +11,8 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) func TestFetcherDynamicLoadListExists(t *testing.T) { diff --git a/go.mod b/go.mod index e52946fbe1e..912cdaa2aad 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/prebid/prebid-server +module github.com/prebid/prebid-server/v2 go 1.20 diff --git a/hooks/empty_plan.go b/hooks/empty_plan.go index 01e01843324..514d3824898 100644 --- a/hooks/empty_plan.go +++ b/hooks/empty_plan.go @@ -1,8 +1,8 @@ package hooks import ( - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) // EmptyPlanBuilder implements the ExecutionPlanBuilder interface diff --git a/hooks/hookanalytics/analytics_test.go b/hooks/hookanalytics/analytics_test.go index abf5e7f2ddc..27584cf0d39 100644 --- a/hooks/hookanalytics/analytics_test.go +++ b/hooks/hookanalytics/analytics_test.go @@ -3,7 +3,7 @@ package hookanalytics import ( "testing" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/hooks/hookexecution/context.go b/hooks/hookexecution/context.go index 5f7cc3ab188..0817078137f 100644 --- a/hooks/hookexecution/context.go +++ b/hooks/hookexecution/context.go @@ -4,8 +4,8 @@ import ( "sync" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) // executionContext holds information passed to module's hook during hook execution. diff --git a/hooks/hookexecution/enricher.go b/hooks/hookexecution/enricher.go index c3dd5a23339..ff7f8dd562e 100644 --- a/hooks/hookexecution/enricher.go +++ b/hooks/hookexecution/enricher.go @@ -5,8 +5,8 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) diff --git a/hooks/hookexecution/enricher_test.go b/hooks/hookexecution/enricher_test.go index 24f0f1c1d45..e0732dfe13b 100644 --- a/hooks/hookexecution/enricher_test.go +++ b/hooks/hookexecution/enricher_test.go @@ -7,10 +7,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookanalytics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/hooks/hookexecution/errors.go b/hooks/hookexecution/errors.go index b1cf912ccee..1d016e26019 100644 --- a/hooks/hookexecution/errors.go +++ b/hooks/hookexecution/errors.go @@ -3,7 +3,7 @@ package hookexecution import ( "fmt" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" ) // TimeoutError indicates exceeding of the max execution time allotted for hook. diff --git a/hooks/hookexecution/execution.go b/hooks/hookexecution/execution.go index 18c927896b9..90ee9b46a9c 100644 --- a/hooks/hookexecution/execution.go +++ b/hooks/hookexecution/execution.go @@ -7,9 +7,9 @@ import ( "sync" "time" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/metrics" ) type hookResponse[T any] struct { diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go index 5074d4b9ab9..dd6953fb73c 100644 --- a/hooks/hookexecution/executor.go +++ b/hooks/hookexecution/executor.go @@ -6,13 +6,13 @@ import ( "sync" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/hooks/hookexecution/executor_test.go b/hooks/hookexecution/executor_test.go index 68b990bb595..6f50e089b40 100644 --- a/hooks/hookexecution/executor_test.go +++ b/hooks/hookexecution/executor_test.go @@ -9,15 +9,15 @@ import ( "time" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookanalytics" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/hooks/hookexecution/mocks_test.go b/hooks/hookexecution/mocks_test.go index ff543c38a07..420a44e8fe9 100644 --- a/hooks/hookexecution/mocks_test.go +++ b/hooks/hookexecution/mocks_test.go @@ -5,8 +5,8 @@ import ( "errors" "time" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type mockUpdateHeaderEntrypointHook struct{} diff --git a/hooks/hookexecution/outcome.go b/hooks/hookexecution/outcome.go index 3eeb7bcef5e..ff8bf1e973e 100644 --- a/hooks/hookexecution/outcome.go +++ b/hooks/hookexecution/outcome.go @@ -3,7 +3,7 @@ package hookexecution import ( "time" - "github.com/prebid/prebid-server/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" ) // Status indicates the result of hook execution. diff --git a/hooks/hookexecution/test_utils.go b/hooks/hookexecution/test_utils.go index 2a604d851f5..32ae9fd7a22 100644 --- a/hooks/hookexecution/test_utils.go +++ b/hooks/hookexecution/test_utils.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/hooks/hookstage/allprocessedbidresponses.go b/hooks/hookstage/allprocessedbidresponses.go index 3f90c5624ee..233a68b6efd 100644 --- a/hooks/hookstage/allprocessedbidresponses.go +++ b/hooks/hookstage/allprocessedbidresponses.go @@ -3,8 +3,8 @@ package hookstage import ( "context" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // AllProcessedBidResponses hooks are invoked over a list of all diff --git a/hooks/hookstage/invocation.go b/hooks/hookstage/invocation.go index 7f465382b20..73b210957e2 100644 --- a/hooks/hookstage/invocation.go +++ b/hooks/hookstage/invocation.go @@ -3,7 +3,7 @@ package hookstage import ( "encoding/json" - "github.com/prebid/prebid-server/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" ) // HookResult represents the result of execution the concrete hook instance. diff --git a/hooks/hookstage/processedauctionrequest.go b/hooks/hookstage/processedauctionrequest.go index fe06bc6fdbd..9ee06fabb2f 100644 --- a/hooks/hookstage/processedauctionrequest.go +++ b/hooks/hookstage/processedauctionrequest.go @@ -3,7 +3,7 @@ package hookstage import ( "context" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // ProcessedAuctionRequest hooks are invoked after the request is parsed diff --git a/hooks/hookstage/rawbidderresponse.go b/hooks/hookstage/rawbidderresponse.go index d450d6d0681..7d08a7d2e02 100644 --- a/hooks/hookstage/rawbidderresponse.go +++ b/hooks/hookstage/rawbidderresponse.go @@ -3,7 +3,7 @@ package hookstage import ( "context" - "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/v2/adapters" ) // RawBidderResponse hooks are invoked for each bidder participating in auction. diff --git a/hooks/hookstage/rawbidderresponse_mutations.go b/hooks/hookstage/rawbidderresponse_mutations.go index 61c0de10bde..efab874fa15 100644 --- a/hooks/hookstage/rawbidderresponse_mutations.go +++ b/hooks/hookstage/rawbidderresponse_mutations.go @@ -3,7 +3,7 @@ package hookstage import ( "errors" - "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/v2/adapters" ) func (c *ChangeSet[T]) RawBidderResponse() ChangeSetRawBidderResponse[T] { diff --git a/hooks/plan.go b/hooks/plan.go index c6fda959762..a3a0e9af661 100644 --- a/hooks/plan.go +++ b/hooks/plan.go @@ -4,8 +4,8 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) type Stage string diff --git a/hooks/plan_test.go b/hooks/plan_test.go index 8af49b42e17..064403cb8cf 100644 --- a/hooks/plan_test.go +++ b/hooks/plan_test.go @@ -5,9 +5,9 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/hooks/repo.go b/hooks/repo.go index 40276701b34..3d8db581bda 100644 --- a/hooks/repo.go +++ b/hooks/repo.go @@ -3,7 +3,7 @@ package hooks import ( "fmt" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) // HookRepository is the interface that exposes methods diff --git a/hooks/repo_test.go b/hooks/repo_test.go index ae523c98773..1ffbf0bfbed 100644 --- a/hooks/repo_test.go +++ b/hooks/repo_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/hooks/hookstage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/macros/provider.go b/macros/provider.go index 0b0fc0de454..3cae540e22a 100644 --- a/macros/provider.go +++ b/macros/provider.go @@ -5,8 +5,8 @@ import ( "strconv" "time" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/macros/provider_test.go b/macros/provider_test.go index b6465a7f2e6..ee9663e3269 100644 --- a/macros/provider_test.go +++ b/macros/provider_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/macros/string_index_based_replacer_test.go b/macros/string_index_based_replacer_test.go index 97379a6d965..c9a05a83df4 100644 --- a/macros/string_index_based_replacer_test.go +++ b/macros/string_index_based_replacer_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/main.go b/main.go index a83266665f0..e72f02f1f0e 100644 --- a/main.go +++ b/main.go @@ -8,12 +8,12 @@ import ( "runtime" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/router" - "github.com/prebid/prebid-server/server" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/router" + "github.com/prebid/prebid-server/v2/server" + "github.com/prebid/prebid-server/v2/util/task" "github.com/golang/glog" "github.com/spf13/viper" diff --git a/main_test.go b/main_test.go index 25812ba96ab..79ae373d473 100644 --- a/main_test.go +++ b/main_test.go @@ -4,7 +4,7 @@ import ( "os" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" "github.com/spf13/viper" diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index d5cb00344ec..7ed387512fd 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -3,10 +3,10 @@ package config import ( "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - prometheusmetrics "github.com/prebid/prebid-server/metrics/prometheus" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + prometheusmetrics "github.com/prebid/prebid-server/v2/metrics/prometheus" + "github.com/prebid/prebid-server/v2/openrtb_ext" gometrics "github.com/rcrowley/go-metrics" influxdb "github.com/vrischmann/go-metrics-influxdb" ) diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index 1eae1a25545..5badc348e61 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - mainConfig "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + mainConfig "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" gometrics "github.com/rcrowley/go-metrics" ) diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 13cc022c9c8..5bc4ab965f6 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -7,8 +7,8 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" metrics "github.com/rcrowley/go-metrics" ) diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 08ee65f52ac..05529220f16 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" metrics "github.com/rcrowley/go-metrics" "github.com/stretchr/testify/assert" ) diff --git a/metrics/metrics.go b/metrics/metrics.go index 0c3025bd296..7d3dc819341 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -3,7 +3,7 @@ package metrics import ( "time" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Labels defines the labels that can be attached to the metrics. diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index eb092f0d972..ef9fdcaf7a4 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -3,7 +3,7 @@ package metrics import ( "time" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/mock" ) diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go index db40feabbd8..a4a70017355 100644 --- a/metrics/prometheus/preload.go +++ b/metrics/prometheus/preload.go @@ -1,8 +1,8 @@ package prometheusmetrics import ( - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prometheus/client_golang/prometheus" ) diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index bd63997f0d2..6bd30544662 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -6,9 +6,9 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prometheus/client_golang/prometheus" promCollector "github.com/prometheus/client_golang/prometheus/collectors" ) diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index a2b0e84d2ea..a74c8b6c0fa 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -5,9 +5,9 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" diff --git a/modules/builder.go b/modules/builder.go index ffb814e6407..e5d04e149af 100644 --- a/modules/builder.go +++ b/modules/builder.go @@ -1,7 +1,7 @@ package modules import ( - prebidOrtb2blocking "github.com/prebid/prebid-server/modules/prebid/ortb2blocking" + prebidOrtb2blocking "github.com/prebid/prebid-server/v2/modules/prebid/ortb2blocking" ) // builders returns mapping between module name and its builder diff --git a/modules/generator/builder.tmpl b/modules/generator/builder.tmpl index f89cc21c87f..b7b78103dbe 100644 --- a/modules/generator/builder.tmpl +++ b/modules/generator/builder.tmpl @@ -3,7 +3,7 @@ package modules {{if .}} import ( {{- range .}} - {{.Vendor}}{{.Module | Title}} "github.com/prebid/prebid-server/modules/{{.Vendor}}/{{.Module}}" + {{.Vendor}}{{.Module | Title}} "github.com/prebid/prebid-server/v2/modules/{{.Vendor}}/{{.Module}}" {{- end}} ) {{end}} diff --git a/modules/helpers.go b/modules/helpers.go index c7fe9f73f31..10890743691 100644 --- a/modules/helpers.go +++ b/modules/helpers.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) var moduleReplacer = strings.NewReplacer(".", "_", "-", "_") diff --git a/modules/modules.go b/modules/modules.go index 4e60a9eda32..f3ccd6b1ece 100644 --- a/modules/modules.go +++ b/modules/modules.go @@ -5,10 +5,10 @@ import ( "fmt" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/modules/moduledeps" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/modules/moduledeps" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) //go:generate go run ./generator/buildergen.go diff --git a/modules/modules_test.go b/modules/modules_test.go index f88a48a2c44..008c1e75c51 100644 --- a/modules/modules_test.go +++ b/modules/modules_test.go @@ -9,10 +9,10 @@ import ( "net/http" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/modules/moduledeps" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/moduledeps" "github.com/stretchr/testify/assert" ) diff --git a/modules/prebid/ortb2blocking/analytics.go b/modules/prebid/ortb2blocking/analytics.go index 4026b6722b9..1309858c7b6 100644 --- a/modules/prebid/ortb2blocking/analytics.go +++ b/modules/prebid/ortb2blocking/analytics.go @@ -1,8 +1,8 @@ package ortb2blocking import ( - "github.com/prebid/prebid-server/hooks/hookanalytics" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) const enforceBlockingTag = "enforce_blocking" diff --git a/modules/prebid/ortb2blocking/config.go b/modules/prebid/ortb2blocking/config.go index 74841eb5b33..4b832cb1977 100644 --- a/modules/prebid/ortb2blocking/config.go +++ b/modules/prebid/ortb2blocking/config.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) func newConfig(data json.RawMessage) (config, error) { diff --git a/modules/prebid/ortb2blocking/hook_bidderrequest.go b/modules/prebid/ortb2blocking/hook_bidderrequest.go index 8f7ce42021c..0bd990eb50c 100644 --- a/modules/prebid/ortb2blocking/hook_bidderrequest.go +++ b/modules/prebid/ortb2blocking/hook_bidderrequest.go @@ -7,9 +7,9 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func handleBidderRequestHook( diff --git a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go index 1c51256211b..615ceda9e04 100644 --- a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go +++ b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go @@ -7,9 +7,9 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) func handleRawBidderResponseHook( diff --git a/modules/prebid/ortb2blocking/module.go b/modules/prebid/ortb2blocking/module.go index 7386aa62bad..93ca15ff31e 100644 --- a/modules/prebid/ortb2blocking/module.go +++ b/modules/prebid/ortb2blocking/module.go @@ -5,8 +5,8 @@ import ( "encoding/json" "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/modules/moduledeps" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/moduledeps" ) func Builder(_ json.RawMessage, _ moduledeps.ModuleDeps) (interface{}, error) { diff --git a/modules/prebid/ortb2blocking/module_test.go b/modules/prebid/ortb2blocking/module_test.go index ecae5ca8c0a..b4499a4bbfb 100644 --- a/modules/prebid/ortb2blocking/module_test.go +++ b/modules/prebid/ortb2blocking/module_test.go @@ -8,11 +8,11 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/hooks/hookanalytics" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/modules/moduledeps" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/moduledeps" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/convert_down_test.go b/openrtb_ext/convert_down_test.go index 3b78337df8b..a72ba3a48ad 100644 --- a/openrtb_ext/convert_down_test.go +++ b/openrtb_ext/convert_down_test.go @@ -6,7 +6,7 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/deal_tier.go b/openrtb_ext/deal_tier.go index df386916b77..b1d8ee11bc2 100644 --- a/openrtb_ext/deal_tier.go +++ b/openrtb_ext/deal_tier.go @@ -2,7 +2,7 @@ package openrtb_ext import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // DealTier defines the configuration of a deal tier. diff --git a/openrtb_ext/deal_tier_test.go b/openrtb_ext/deal_tier_test.go index dabecf6a9e7..b8607748ca9 100644 --- a/openrtb_ext/deal_tier_test.go +++ b/openrtb_ext/deal_tier_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/device.go b/openrtb_ext/device.go index 8c5b36733b9..0888d06160f 100644 --- a/openrtb_ext/device.go +++ b/openrtb_ext/device.go @@ -6,7 +6,7 @@ import ( "strconv" "github.com/buger/jsonparser" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" ) // PrebidExtKey represents the prebid extension key used in requests diff --git a/openrtb_ext/device_test.go b/openrtb_ext/device_test.go index 86a0e1d7ff2..f40e9650061 100644 --- a/openrtb_ext/device_test.go +++ b/openrtb_ext/device_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/imp_appnexus.go b/openrtb_ext/imp_appnexus.go index 02476d6bcf0..db949f661fd 100644 --- a/openrtb_ext/imp_appnexus.go +++ b/openrtb_ext/imp_appnexus.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // ExtImpAppnexus defines the contract for bidrequest.imp[i].ext.prebid.bidder.appnexus diff --git a/openrtb_ext/imp_appnexus_test.go b/openrtb_ext/imp_appnexus_test.go index a226c0d8410..9d65f3be3ad 100644 --- a/openrtb_ext/imp_appnexus_test.go +++ b/openrtb_ext/imp_appnexus_test.go @@ -3,7 +3,7 @@ package openrtb_ext import ( "testing" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/imp_freewheelssp.go b/openrtb_ext/imp_freewheelssp.go index 110f018f512..3d015d96722 100644 --- a/openrtb_ext/imp_freewheelssp.go +++ b/openrtb_ext/imp_freewheelssp.go @@ -1,7 +1,7 @@ package openrtb_ext import ( - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) type ImpExtFreewheelSSP struct { diff --git a/openrtb_ext/multibid_test.go b/openrtb_ext/multibid_test.go index a27631d6891..926ab261b0f 100644 --- a/openrtb_ext/multibid_test.go +++ b/openrtb_ext/multibid_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 3a41fdf0dbb..1a98e34d13f 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -5,10 +5,10 @@ import ( "fmt" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/maputil" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/maputil" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" ) // FirstPartyDataExtKey defines a field name within request.ext and request.imp.ext reserved for first party data. diff --git a/openrtb_ext/request_test.go b/openrtb_ext/request_test.go index ad6a655b022..3163150e57c 100644 --- a/openrtb_ext/request_test.go +++ b/openrtb_ext/request_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go index 09321e5b0b3..5adc192d36f 100644 --- a/openrtb_ext/request_wrapper.go +++ b/openrtb_ext/request_wrapper.go @@ -5,10 +5,10 @@ import ( "errors" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/maputil" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/maputil" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" ) // RequestWrapper wraps the OpenRTB request to provide a storage location for unmarshalled ext fields, so they diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go index 0127c545274..bd55c86beb2 100644 --- a/openrtb_ext/request_wrapper_test.go +++ b/openrtb_ext/request_wrapper_test.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/site_test.go b/openrtb_ext/site_test.go index 7a7140282f2..f6fb04c50ee 100644 --- a/openrtb_ext/site_test.go +++ b/openrtb_ext/site_test.go @@ -3,8 +3,8 @@ package openrtb_ext_test import ( "testing" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/supplyChain.go b/openrtb_ext/supplyChain.go index 6f023542dfb..0ccbd0957fa 100644 --- a/openrtb_ext/supplyChain.go +++ b/openrtb_ext/supplyChain.go @@ -2,7 +2,7 @@ package openrtb_ext import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" ) func cloneSupplyChain(schain *openrtb2.SupplyChain) *openrtb2.SupplyChain { diff --git a/openrtb_ext/supplyChain_test.go b/openrtb_ext/supplyChain_test.go index 12fd5c337fb..728fbc68cc4 100644 --- a/openrtb_ext/supplyChain_test.go +++ b/openrtb_ext/supplyChain_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/ortb/clone.go b/ortb/clone.go index c0e5a4ddada..0dd210a986e 100644 --- a/ortb/clone.go +++ b/ortb/clone.go @@ -2,8 +2,8 @@ package ortb import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" ) func CloneApp(s *openrtb2.App) *openrtb2.App { diff --git a/ortb/clone_test.go b/ortb/clone_test.go index 24e43bda1e5..50dd94057ee 100644 --- a/ortb/clone_test.go +++ b/ortb/clone_test.go @@ -7,7 +7,7 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/ortb/default.go b/ortb/default.go index cd9d8c24759..c5e43e2e770 100644 --- a/ortb/default.go +++ b/ortb/default.go @@ -1,8 +1,8 @@ package ortb import ( - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" ) const ( diff --git a/ortb/default_test.go b/ortb/default_test.go index 8bda02ef4f5..2d99da4bec2 100644 --- a/ortb/default_test.go +++ b/ortb/default_test.go @@ -8,10 +8,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" ) func TestSetDefaults(t *testing.T) { diff --git a/pbs/usersync.go b/pbs/usersync.go index a5b49f6db03..bfe12689177 100644 --- a/pbs/usersync.go +++ b/pbs/usersync.go @@ -10,9 +10,9 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/server/ssl" - "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/server/ssl" + "github.com/prebid/prebid-server/v2/usersync" ) // Recaptcha code from https://github.com/haisum/recaptcha/blob/master/recaptcha.go diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index 872420001ea..fb3fb24d9cc 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -12,8 +12,8 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" "github.com/buger/jsonparser" "github.com/golang/glog" diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index f20d3c7829f..f3ee3065ff1 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -10,10 +10,10 @@ import ( "strconv" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - metricsConf "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/privacy/activitycontrol.go b/privacy/activitycontrol.go index 9d6668b3e44..1bb3fc6cdf6 100644 --- a/privacy/activitycontrol.go +++ b/privacy/activitycontrol.go @@ -1,8 +1,8 @@ package privacy import ( - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type ActivityResult int diff --git a/privacy/activitycontrol_test.go b/privacy/activitycontrol_test.go index 743888df029..b8b06ee8886 100644 --- a/privacy/activitycontrol_test.go +++ b/privacy/activitycontrol_test.go @@ -3,9 +3,9 @@ package privacy import ( "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go index 1d65a272f90..339eb3438fb 100644 --- a/privacy/ccpa/consentwriter.go +++ b/privacy/ccpa/consentwriter.go @@ -2,7 +2,7 @@ package ccpa import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // ConsentWriter implements the old PolicyWriter interface for CCPA. diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go index 015f1328f61..e8414a8e2f5 100644 --- a/privacy/ccpa/consentwriter_test.go +++ b/privacy/ccpa/consentwriter_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/privacy/ccpa/parsedpolicy.go b/privacy/ccpa/parsedpolicy.go index 7b9c2d1fa7c..056cc99ee1b 100644 --- a/privacy/ccpa/parsedpolicy.go +++ b/privacy/ccpa/parsedpolicy.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" ) const ( diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index fbafd8a8a2e..e5412b7d4c7 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -7,9 +7,9 @@ import ( gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - gppPolicy "github.com/prebid/prebid-server/privacy/gpp" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + gppPolicy "github.com/prebid/prebid-server/v2/privacy/gpp" ) // Policy represents the CCPA regulatory information from an OpenRTB bid request. diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index 3a1433333c0..e18820b221b 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -8,7 +8,7 @@ import ( gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/privacy/enforcement.go b/privacy/enforcement.go index 8074d96acf3..901c062ea4e 100644 --- a/privacy/enforcement.go +++ b/privacy/enforcement.go @@ -2,7 +2,7 @@ package privacy import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) // Enforcement represents the privacy policies to enforce for an OpenRTB bid request. diff --git a/privacy/gdpr/consentwriter.go b/privacy/gdpr/consentwriter.go index 00e3558fd40..25bc2bf0ca0 100644 --- a/privacy/gdpr/consentwriter.go +++ b/privacy/gdpr/consentwriter.go @@ -2,7 +2,7 @@ package gdpr import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // ConsentWriter implements the PolicyWriter interface for GDPR TCF. diff --git a/privacy/lmt/ios.go b/privacy/lmt/ios.go index 0b308a9ce32..ee08225f8c7 100644 --- a/privacy/lmt/ios.go +++ b/privacy/lmt/ios.go @@ -4,8 +4,8 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/iosutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/iosutil" ) var ( diff --git a/privacy/lmt/ios_test.go b/privacy/lmt/ios_test.go index 2a679bfbd99..7afaf7843e1 100644 --- a/privacy/lmt/ios_test.go +++ b/privacy/lmt/ios_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/iosutil" + "github.com/prebid/prebid-server/v2/util/iosutil" "github.com/stretchr/testify/assert" ) diff --git a/privacy/rule_condition_test.go b/privacy/rule_condition_test.go index bb1d81c00d2..23973bd06d0 100644 --- a/privacy/rule_condition_test.go +++ b/privacy/rule_condition_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/privacy/scrubber.go b/privacy/scrubber.go index 0cfc7cdd7f4..54941669ab9 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -4,10 +4,10 @@ import ( "encoding/json" "net" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/prebid/openrtb/v19/openrtb2" ) diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index 59e593fc167..299acd9eba3 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -2,9 +2,10 @@ package privacy import ( "encoding/json" - "github.com/prebid/prebid-server/config" "testing" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/router/admin.go b/router/admin.go index 29cdbbe5e23..1be7c8656da 100644 --- a/router/admin.go +++ b/router/admin.go @@ -5,9 +5,9 @@ import ( "net/http/pprof" "time" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/endpoints" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/endpoints" + "github.com/prebid/prebid-server/v2/version" ) func Admin(rateConverter *currency.RateConverter, rateConverterFetchingInterval time.Duration) *http.ServeMux { diff --git a/router/aspects/request_timeout_handler.go b/router/aspects/request_timeout_handler.go index 39a4341f995..7b94c96b11b 100644 --- a/router/aspects/request_timeout_handler.go +++ b/router/aspects/request_timeout_handler.go @@ -6,8 +6,8 @@ import ( "time" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" ) func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestTimeoutHeaders, metricsEngine metrics.MetricsEngine, requestType metrics.RequestType) httprouter.Handle { diff --git a/router/aspects/request_timeout_handler_test.go b/router/aspects/request_timeout_handler_test.go index 26e546dcd40..4ece14208e8 100644 --- a/router/aspects/request_timeout_handler_test.go +++ b/router/aspects/request_timeout_handler_test.go @@ -8,8 +8,8 @@ import ( "time" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" "github.com/stretchr/testify/assert" ) diff --git a/router/router.go b/router/router.go index 70b7860d661..0ee9fc7c30e 100644 --- a/router/router.go +++ b/router/router.go @@ -10,33 +10,33 @@ import ( "strings" "time" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/endpoints" - "github.com/prebid/prebid-server/endpoints/events" - infoEndpoints "github.com/prebid/prebid-server/endpoints/info" - "github.com/prebid/prebid-server/endpoints/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - metricsConf "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/modules" - "github.com/prebid/prebid-server/modules/moduledeps" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - pbc "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/router/aspects" - "github.com/prebid/prebid-server/server/ssl" - storedRequestsConf "github.com/prebid/prebid-server/stored_requests/config" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/endpoints" + "github.com/prebid/prebid-server/v2/endpoints/events" + infoEndpoints "github.com/prebid/prebid-server/v2/endpoints/info" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/modules" + "github.com/prebid/prebid-server/v2/modules/moduledeps" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/pbs" + pbc "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/router/aspects" + "github.com/prebid/prebid-server/v2/server/ssl" + storedRequestsConf "github.com/prebid/prebid-server/v2/stored_requests/config" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/uuidutil" + "github.com/prebid/prebid-server/v2/version" _ "github.com/go-sql-driver/mysql" "github.com/golang/glog" diff --git a/router/router_test.go b/router/router_test.go index f4f7715e6c3..cc2f077e5e6 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -7,9 +7,9 @@ import ( "os" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/schain/schain.go b/schain/schain.go index 6f084a65a2a..a4139a93f5e 100644 --- a/schain/schain.go +++ b/schain/schain.go @@ -5,7 +5,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // BidderToPrebidSChains organizes the ORTB 2.5 multiple root schain nodes into a map of schain nodes by bidder diff --git a/schain/schain_test.go b/schain/schain_test.go index dbe38d4014b..310608420d9 100644 --- a/schain/schain_test.go +++ b/schain/schain_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/schain/schainwriter.go b/schain/schainwriter.go index e7c9dd4ce72..0873e14f199 100644 --- a/schain/schainwriter.go +++ b/schain/schainwriter.go @@ -2,8 +2,8 @@ package schain import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // NewSChainWriter creates an ORTB 2.5 schain writer instance diff --git a/schain/schainwriter_test.go b/schain/schainwriter_test.go index 9288b531d56..26777306fdf 100644 --- a/schain/schainwriter_test.go +++ b/schain/schainwriter_test.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/scripts/check_coverage.sh b/scripts/check_coverage.sh index 0dd6235b96b..63e89297c42 100755 --- a/scripts/check_coverage.sh +++ b/scripts/check_coverage.sh @@ -25,8 +25,7 @@ while IFS= read -r LINE; do if [[ $LINE =~ "%" ]]; then PERCENT=$(echo "$LINE"|cut -d: -f2-|cut -d% -f1|cut -d. -f1|tr -d ' ') if [[ $PERCENT -lt $COV_MIN ]]; then - echo "Package has less than ${COV_MIN}% code coverage. Run ./scripts/coverage.sh --html to see a detailed coverage report, and add tests to improve your coverage" - exit 1 + echo "WARNING: Package has less than ${COV_MIN}% code coverage. Run ./scripts/coverage.sh --html to see a detailed coverage report, and add tests to improve your coverage" fi fi done <<< "$OUTPUT" diff --git a/server/listener.go b/server/listener.go index 43917ac0a05..a10aef6441e 100644 --- a/server/listener.go +++ b/server/listener.go @@ -6,7 +6,7 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/metrics" ) // monitorableListener tracks any opened connections in the metrics. diff --git a/server/listener_test.go b/server/listener_test.go index d10a3bdfbf9..c729f2ba55e 100644 --- a/server/listener_test.go +++ b/server/listener_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" gometrics "github.com/rcrowley/go-metrics" ) diff --git a/server/prometheus.go b/server/prometheus.go index 33114c86a0b..8b841f5151a 100644 --- a/server/prometheus.go +++ b/server/prometheus.go @@ -7,8 +7,8 @@ import ( "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/prebid/prebid-server/config" - metricsconfig "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/v2/config" + metricsconfig "github.com/prebid/prebid-server/v2/metrics/config" ) func newPrometheusServer(cfg *config.Configuration, metrics *metricsconfig.DetailedMetricsEngine) *http.Server { diff --git a/server/server.go b/server/server.go index 9282f0fcf15..dd4813adb7f 100644 --- a/server/server.go +++ b/server/server.go @@ -13,9 +13,9 @@ import ( "github.com/NYTimes/gziphandler" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - metricsconfig "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + metricsconfig "github.com/prebid/prebid-server/v2/metrics/config" ) // Listen blocks forever, serving PBS requests on the given port. This will block forever, until the process is shut down. diff --git a/server/server_test.go b/server/server_test.go index 7af892d3567..03a2fc911b5 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - metricsconfig "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/v2/config" + metricsconfig "github.com/prebid/prebid-server/v2/metrics/config" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/backends/db_fetcher/fetcher.go b/stored_requests/backends/db_fetcher/fetcher.go index 3ea36bc0fc7..3a9b83f7779 100644 --- a/stored_requests/backends/db_fetcher/fetcher.go +++ b/stored_requests/backends/db_fetcher/fetcher.go @@ -7,8 +7,8 @@ import ( "github.com/lib/pq" "github.com/golang/glog" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" ) func NewFetcher( diff --git a/stored_requests/backends/db_fetcher/fetcher_test.go b/stored_requests/backends/db_fetcher/fetcher_test.go index 04753fb8af5..f736e1bea15 100644 --- a/stored_requests/backends/db_fetcher/fetcher_test.go +++ b/stored_requests/backends/db_fetcher/fetcher_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/backends/db_provider/db_provider.go b/stored_requests/backends/db_provider/db_provider.go index 0f79a7737e0..6e623356ed8 100644 --- a/stored_requests/backends/db_provider/db_provider.go +++ b/stored_requests/backends/db_provider/db_provider.go @@ -5,7 +5,7 @@ import ( "database/sql" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) type DbProvider interface { diff --git a/stored_requests/backends/db_provider/db_provider_mock.go b/stored_requests/backends/db_provider/db_provider_mock.go index 3d4cfda76c3..c0ec3458eee 100644 --- a/stored_requests/backends/db_provider/db_provider_mock.go +++ b/stored_requests/backends/db_provider/db_provider_mock.go @@ -6,7 +6,7 @@ import ( "reflect" "github.com/DATA-DOG/go-sqlmock" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) func NewDbProviderMock() (*DbProviderMock, sqlmock.Sqlmock, error) { diff --git a/stored_requests/backends/db_provider/mysql_dbprovider.go b/stored_requests/backends/db_provider/mysql_dbprovider.go index 6301a119c45..38ad0836024 100644 --- a/stored_requests/backends/db_provider/mysql_dbprovider.go +++ b/stored_requests/backends/db_provider/mysql_dbprovider.go @@ -15,7 +15,7 @@ import ( "strings" "github.com/go-sql-driver/mysql" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) const customTLSKey = "prebid-tls" diff --git a/stored_requests/backends/db_provider/mysql_dbprovider_test.go b/stored_requests/backends/db_provider/mysql_dbprovider_test.go index e47280ef26b..b91352d08d6 100644 --- a/stored_requests/backends/db_provider/mysql_dbprovider_test.go +++ b/stored_requests/backends/db_provider/mysql_dbprovider_test.go @@ -6,7 +6,7 @@ import ( "runtime" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/backends/db_provider/postgres_dbprovider.go b/stored_requests/backends/db_provider/postgres_dbprovider.go index ef945faebc9..e2081f27df4 100644 --- a/stored_requests/backends/db_provider/postgres_dbprovider.go +++ b/stored_requests/backends/db_provider/postgres_dbprovider.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) type PostgresDbProvider struct { diff --git a/stored_requests/backends/db_provider/postgres_dbprovider_test.go b/stored_requests/backends/db_provider/postgres_dbprovider_test.go index 9e98c0b5763..4b31e6f8ec3 100644 --- a/stored_requests/backends/db_provider/postgres_dbprovider_test.go +++ b/stored_requests/backends/db_provider/postgres_dbprovider_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/backends/empty_fetcher/fetcher.go b/stored_requests/backends/empty_fetcher/fetcher.go index 0246990c02e..c851a997df9 100644 --- a/stored_requests/backends/empty_fetcher/fetcher.go +++ b/stored_requests/backends/empty_fetcher/fetcher.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests" ) // EmptyFetcher is a nil-object which has no Stored Requests. diff --git a/stored_requests/backends/file_fetcher/fetcher.go b/stored_requests/backends/file_fetcher/fetcher.go index a9bbe919dcf..21a54039cda 100644 --- a/stored_requests/backends/file_fetcher/fetcher.go +++ b/stored_requests/backends/file_fetcher/fetcher.go @@ -7,8 +7,8 @@ import ( "os" "strings" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) diff --git a/stored_requests/backends/file_fetcher/fetcher_test.go b/stored_requests/backends/file_fetcher/fetcher_test.go index a69945641f1..0155c1aa82c 100644 --- a/stored_requests/backends/file_fetcher/fetcher_test.go +++ b/stored_requests/backends/file_fetcher/fetcher_test.go @@ -6,8 +6,8 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index 88afa39fb1d..75aa1090ce4 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -9,8 +9,8 @@ import ( "net/url" "strings" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" "github.com/golang/glog" diff --git a/stored_requests/backends/http_fetcher/fetcher_test.go b/stored_requests/backends/http_fetcher/fetcher_test.go index 80be6918ad8..1358ce413c1 100644 --- a/stored_requests/backends/http_fetcher/fetcher_test.go +++ b/stored_requests/backends/http_fetcher/fetcher_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/caches/cachestest/reliable.go b/stored_requests/caches/cachestest/reliable.go index 7fbaf7238af..517668318c7 100644 --- a/stored_requests/caches/cachestest/reliable.go +++ b/stored_requests/caches/cachestest/reliable.go @@ -5,7 +5,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests" ) const ( diff --git a/stored_requests/caches/memory/cache.go b/stored_requests/caches/memory/cache.go index 5939c26ddec..eb6317a3487 100644 --- a/stored_requests/caches/memory/cache.go +++ b/stored_requests/caches/memory/cache.go @@ -7,7 +7,7 @@ import ( "github.com/coocood/freecache" "github.com/golang/glog" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests" ) // NewCache returns an in-memory Cache which evicts items if: diff --git a/stored_requests/caches/memory/cache_test.go b/stored_requests/caches/memory/cache_test.go index b89bd5af26f..67ff661c7c5 100644 --- a/stored_requests/caches/memory/cache_test.go +++ b/stored_requests/caches/memory/cache_test.go @@ -7,8 +7,8 @@ import ( "strconv" "testing" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/caches/cachestest" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/caches/cachestest" ) func TestLRURobustness(t *testing.T) { diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index 9cb349d1f72..112b11122f9 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -5,24 +5,24 @@ import ( "net/http" "time" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/metrics" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/db_fetcher" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" - "github.com/prebid/prebid-server/stored_requests/backends/http_fetcher" - "github.com/prebid/prebid-server/stored_requests/caches/memory" - "github.com/prebid/prebid-server/stored_requests/caches/nil_cache" - "github.com/prebid/prebid-server/stored_requests/events" - apiEvents "github.com/prebid/prebid-server/stored_requests/events/api" - databaseEvents "github.com/prebid/prebid-server/stored_requests/events/database" - httpEvents "github.com/prebid/prebid-server/stored_requests/events/http" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_fetcher" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/stored_requests/backends/file_fetcher" + "github.com/prebid/prebid-server/v2/stored_requests/backends/http_fetcher" + "github.com/prebid/prebid-server/v2/stored_requests/caches/memory" + "github.com/prebid/prebid-server/v2/stored_requests/caches/nil_cache" + "github.com/prebid/prebid-server/v2/stored_requests/events" + apiEvents "github.com/prebid/prebid-server/v2/stored_requests/events/api" + databaseEvents "github.com/prebid/prebid-server/v2/stored_requests/events/database" + httpEvents "github.com/prebid/prebid-server/v2/stored_requests/events/http" + "github.com/prebid/prebid-server/v2/util/task" ) // CreateStoredRequests returns three things: diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go index b06feea7d31..2e5a8c2a079 100644 --- a/stored_requests/config/config_test.go +++ b/stored_requests/config/config_test.go @@ -13,14 +13,14 @@ import ( sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_requests/backends/http_fetcher" - "github.com/prebid/prebid-server/stored_requests/events" - httpEvents "github.com/prebid/prebid-server/stored_requests/events/http" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/stored_requests/backends/http_fetcher" + "github.com/prebid/prebid-server/v2/stored_requests/events" + httpEvents "github.com/prebid/prebid-server/v2/stored_requests/events/http" "github.com/stretchr/testify/mock" ) diff --git a/stored_requests/events/api/api.go b/stored_requests/events/api/api.go index 30778f0e11a..2d489bded38 100644 --- a/stored_requests/events/api/api.go +++ b/stored_requests/events/api/api.go @@ -5,8 +5,8 @@ import ( "net/http" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/stored_requests/events" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/stored_requests/events" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) type eventsAPI struct { diff --git a/stored_requests/events/api/api_test.go b/stored_requests/events/api/api_test.go index 67ba836cb00..536afe820af 100644 --- a/stored_requests/events/api/api_test.go +++ b/stored_requests/events/api/api_test.go @@ -9,9 +9,9 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/caches/memory" - "github.com/prebid/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/caches/memory" + "github.com/prebid/prebid-server/v2/stored_requests/events" ) func TestGoodRequests(t *testing.T) { diff --git a/stored_requests/events/database/database.go b/stored_requests/events/database/database.go index 24eddf214eb..260a58029cb 100644 --- a/stored_requests/events/database/database.go +++ b/stored_requests/events/database/database.go @@ -9,11 +9,11 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" - "github.com/prebid/prebid-server/stored_requests/events" - "github.com/prebid/prebid-server/util/timeutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v2/stored_requests/events" + "github.com/prebid/prebid-server/v2/util/timeutil" ) func bytesNull() []byte { diff --git a/stored_requests/events/database/database_test.go b/stored_requests/events/database/database_test.go index 8ce21bfde95..27a56b67fe3 100644 --- a/stored_requests/events/database/database_test.go +++ b/stored_requests/events/database/database_test.go @@ -7,10 +7,10 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" - "github.com/prebid/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v2/stored_requests/events" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/stored_requests/events/events.go b/stored_requests/events/events.go index 725df4279d8..e042b7c3513 100644 --- a/stored_requests/events/events.go +++ b/stored_requests/events/events.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests" ) // Save represents a bulk save diff --git a/stored_requests/events/events_test.go b/stored_requests/events/events_test.go index 580f1eddf37..165e6a6beb0 100644 --- a/stored_requests/events/events_test.go +++ b/stored_requests/events/events_test.go @@ -7,8 +7,8 @@ import ( "reflect" "testing" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/caches/memory" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/caches/memory" ) func TestListen(t *testing.T) { diff --git a/stored_requests/events/http/http.go b/stored_requests/events/http/http.go index be5b85a03a6..6c2145da2f3 100644 --- a/stored_requests/events/http/http.go +++ b/stored_requests/events/http/http.go @@ -12,8 +12,8 @@ import ( "golang.org/x/net/context/ctxhttp" "github.com/buger/jsonparser" - "github.com/prebid/prebid-server/stored_requests/events" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/stored_requests/events" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/golang/glog" ) diff --git a/stored_requests/events/http/http_test.go b/stored_requests/events/http/http_test.go index 663d8cd193f..fa7f64f227f 100644 --- a/stored_requests/events/http/http_test.go +++ b/stored_requests/events/http/http_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/fetcher.go b/stored_requests/fetcher.go index 433b33427b8..cd3855c9eb8 100644 --- a/stored_requests/fetcher.go +++ b/stored_requests/fetcher.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/metrics" ) // Fetcher knows how to fetch Stored Request data by id. diff --git a/stored_requests/fetcher_test.go b/stored_requests/fetcher_test.go index 684b867165c..34c15f61f96 100644 --- a/stored_requests/fetcher_test.go +++ b/stored_requests/fetcher_test.go @@ -6,8 +6,8 @@ import ( "errors" "testing" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests/caches/nil_cache" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/stored_requests/caches/nil_cache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/stored_responses/stored_responses.go b/stored_responses/stored_responses.go index d6944a6f30f..802602904bc 100644 --- a/stored_responses/stored_responses.go +++ b/stored_responses/stored_responses.go @@ -7,8 +7,8 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/stored_requests" ) type ImpsWithAuctionResponseIDs map[string]string diff --git a/stored_responses/stored_responses_test.go b/stored_responses/stored_responses_test.go index 209b34a650b..196466b7323 100644 --- a/stored_responses/stored_responses_test.go +++ b/stored_responses/stored_responses_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/usersync/chooser.go b/usersync/chooser.go index 578c63717de..3b97359e1ce 100644 --- a/usersync/chooser.go +++ b/usersync/chooser.go @@ -1,8 +1,9 @@ package usersync import ( - "github.com/prebid/prebid-server/openrtb_ext" "strings" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Chooser determines which syncers are eligible for a given request. diff --git a/usersync/chooser_test.go b/usersync/chooser_test.go index 6ab5a6b53e3..e73122b784a 100644 --- a/usersync/chooser_test.go +++ b/usersync/chooser_test.go @@ -1,13 +1,14 @@ package usersync import ( - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "testing" "time" - "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/prebid/prebid-server/v2/macros" ) func TestNewChooser(t *testing.T) { diff --git a/usersync/cookie.go b/usersync/cookie.go index 88524018c49..b4bc821c9d7 100644 --- a/usersync/cookie.go +++ b/usersync/cookie.go @@ -5,9 +5,9 @@ import ( "net/http" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) const uidCookieName = "uids" diff --git a/usersync/cookie_test.go b/usersync/cookie_test.go index 340069767b1..1ecfe51b5b5 100644 --- a/usersync/cookie_test.go +++ b/usersync/cookie_test.go @@ -7,8 +7,8 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/usersync/decoder.go b/usersync/decoder.go index c803fbe2a52..a73e37fc560 100644 --- a/usersync/decoder.go +++ b/usersync/decoder.go @@ -3,7 +3,7 @@ package usersync import ( "encoding/base64" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) type Decoder interface { diff --git a/usersync/encoder.go b/usersync/encoder.go index 74472f23307..2baf5d86524 100644 --- a/usersync/encoder.go +++ b/usersync/encoder.go @@ -3,7 +3,7 @@ package usersync import ( "encoding/base64" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) type Encoder interface { diff --git a/usersync/syncer.go b/usersync/syncer.go index e561614f4a2..3add47e5873 100644 --- a/usersync/syncer.go +++ b/usersync/syncer.go @@ -9,8 +9,8 @@ import ( "text/template" validator "github.com/asaskevich/govalidator" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/macros" ) var ( diff --git a/usersync/syncer_test.go b/usersync/syncer_test.go index 72167addae5..6aa3103211e 100644 --- a/usersync/syncer_test.go +++ b/usersync/syncer_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/macros" "github.com/stretchr/testify/assert" ) diff --git a/usersync/syncersbuilder.go b/usersync/syncersbuilder.go index 9a52a740f31..9c916b821b4 100644 --- a/usersync/syncersbuilder.go +++ b/usersync/syncersbuilder.go @@ -5,7 +5,7 @@ import ( "sort" "strings" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) type namedSyncerConfig struct { diff --git a/usersync/syncersbuilder_test.go b/usersync/syncersbuilder_test.go index 15c53dba2a4..a8f396aa714 100644 --- a/usersync/syncersbuilder_test.go +++ b/usersync/syncersbuilder_test.go @@ -4,8 +4,8 @@ import ( "errors" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/macros" "github.com/stretchr/testify/assert" ) diff --git a/util/httputil/httputil.go b/util/httputil/httputil.go index 28334a54b87..cabb197fa37 100644 --- a/util/httputil/httputil.go +++ b/util/httputil/httputil.go @@ -5,7 +5,7 @@ import ( "net/http" "strings" - "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/v2/util/iputil" ) var ( diff --git a/util/httputil/httputil_test.go b/util/httputil/httputil_test.go index 5da3b0ab735..d056db245f3 100644 --- a/util/httputil/httputil_test.go +++ b/util/httputil/httputil_test.go @@ -5,7 +5,7 @@ import ( "net/http" "testing" - "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/v2/util/iputil" "github.com/stretchr/testify/assert" ) diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go index a8981477dc6..b5bb47cca9a 100644 --- a/util/jsonutil/jsonutil.go +++ b/util/jsonutil/jsonutil.go @@ -7,7 +7,7 @@ import ( "strings" jsoniter "github.com/json-iterator/go" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" ) var comma = byte(',') diff --git a/util/task/ticker_task_test.go b/util/task/ticker_task_test.go index c02eec158a1..3ca0280d23e 100644 --- a/util/task/ticker_task_test.go +++ b/util/task/ticker_task_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v2/util/task" "github.com/stretchr/testify/assert" ) diff --git a/version/xprebidheader.go b/version/xprebidheader.go index fc71bacb1a3..613a76d80b6 100644 --- a/version/xprebidheader.go +++ b/version/xprebidheader.go @@ -5,7 +5,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const xPrebidHeaderVersionPrefix = "pbs-go" diff --git a/version/xprebidheader_test.go b/version/xprebidheader_test.go index a1c7b355bb8..db31ea63620 100644 --- a/version/xprebidheader_test.go +++ b/version/xprebidheader_test.go @@ -6,8 +6,8 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) func TestBuildXPrebidHeader(t *testing.T) { From 2540dd884950f6c582d01263479f3e7973cda2eb Mon Sep 17 00:00:00 2001 From: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Date: Mon, 30 Oct 2023 09:00:24 +0100 Subject: [PATCH 076/138] RTBHouse: native support (#3212) --- adapters/rtbhouse/rtbhouse.go | 65 +++++++- .../bidfloor-as-bidder-param-without-cur.json | 130 ++++++++++++++++ .../exemplary/bidfloor-as-bidder-param.json | 133 +++++++++++++++++ .../bidfloor-as-impbidfloor-with-cur.json | 130 ++++++++++++++++ .../bidfloor-as-impbidfloor-without-cur.json | 128 ++++++++++++++++ .../exemplary/currency-conversion.json | 123 --------------- .../native-with-deprecated-native-prop.json | 100 +++++++++++++ .../native-with-proper-native-response.json | 100 +++++++++++++ .../rtbhousetest/exemplary/simple-banner.json | 6 +- ...bidfloors-given-param-and-impbidfloor.json | 132 ++++++++++++++++ ...ner-native-req-faulty-mtype-in-native.json | 141 ++++++++++++++++++ .../faulty-request-bidder-params.json | 30 ++++ .../faulty-request-no-impext.json | 25 ++++ .../native-with-faulty-adm-native-prop.json | 90 +++++++++++ .../native-with-faulty-adm-response.json | 90 +++++++++++ .../supplemental/simple-banner-bad-mtype.json | 94 ++++++++++++ .../supplemental/simple-banner-no-mtype.json | 93 ++++++++++++ static/bidder-info/rtbhouse.yaml | 1 + 18 files changed, 1481 insertions(+), 130 deletions(-) create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param-without-cur.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-with-cur.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-without-cur.json delete mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/currency-conversion.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/native-with-deprecated-native-prop.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/native-with-proper-native-response.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/two-bidfloors-given-param-and-impbidfloor.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/banner-native-req-faulty-mtype-in-native.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-bidder-params.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-no-impext.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-native-prop.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-response.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-bad-mtype.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-no-mtype.json diff --git a/adapters/rtbhouse/rtbhouse.go b/adapters/rtbhouse/rtbhouse.go index 089634cbd86..92e0e57b287 100644 --- a/adapters/rtbhouse/rtbhouse.go +++ b/adapters/rtbhouse/rtbhouse.go @@ -2,10 +2,12 @@ package rtbhouse import ( "encoding/json" + "errors" "fmt" "net/http" "strings" + "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/config" @@ -49,9 +51,12 @@ func (adapter *RTBHouseAdapter) MakeRequests( if err != nil { return nil, []error{err} } - if rtbhouseExt.BidFloor > 0 && len(reqCopy.Cur) > 0 { - bidFloorCur = reqCopy.Cur[0] + if rtbhouseExt.BidFloor > 0 { bidFloor = rtbhouseExt.BidFloor + bidFloorCur = BidderCurrency + if len(reqCopy.Cur) > 0 { + bidFloorCur = reqCopy.Cur[0] + } } } @@ -156,13 +161,63 @@ func (adapter *RTBHouseAdapter) MakeBids( for _, seatBid := range openRTBBidderResponse.SeatBid { for _, bid := range seatBid.Bid { bid := bid // pin! -> https://github.com/kyoh86/scopelint#whats-this - typedBid = &adapters.TypedBid{Bid: &bid, BidType: "banner"} - bidderResponse.Bids = append(bidderResponse.Bids, typedBid) + bidType, err := getMediaTypeForBid(bid) + if err != nil { + errs = append(errs, err) + continue + } else { + typedBid = &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + } + + // for native bid responses fix Adm field + if typedBid.BidType == openrtb_ext.BidTypeNative { + bid.AdM, err = getNativeAdm(bid.AdM) + if err != nil { + errs = append(errs, err) + continue + } + } + + bidderResponse.Bids = append(bidderResponse.Bids, typedBid) + } } } bidderResponse.Currency = BidderCurrency - return bidderResponse, nil + return bidderResponse, errs + +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("unrecognized bid type in response from rtbhouse for bid %s", bid.ImpID) + } +} + +func getNativeAdm(adm string) (string, error) { + nativeAdm := make(map[string]interface{}) + err := json.Unmarshal([]byte(adm), &nativeAdm) + if err != nil { + return adm, errors.New("unable to unmarshal native adm") + } + + // move bid.adm.native to bid.adm + if _, ok := nativeAdm["native"]; ok { + //using jsonparser to avoid marshaling, encode escape, etc. + value, dataType, _, err := jsonparser.Get([]byte(adm), string(openrtb_ext.BidTypeNative)) + if err != nil || dataType != jsonparser.Object { + return adm, errors.New("unable to get native adm") + } + adm = string(value) + } + return adm, nil } diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param-without-cur.json b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param-without-cur.json new file mode 100644 index 00000000000..79aa038c3ca --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param-without-cur.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "12345", + "bidfloor": 3.00 + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "bidfloor": 3.00, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "publisherId": "12345", + "bidfloor": 3.00 + } + }, + "id": "test-imp-id" + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param.json b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param.json new file mode 100644 index 00000000000..99b3a87ebfa --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "cur": [ + "EUR" + ], + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "12345", + "bidfloor": 2 + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "bidfloor": 0.1, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "publisherId": "12345", + "bidfloor": 2 + } + }, + "id": "test-imp-id" + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-with-cur.json b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-with-cur.json new file mode 100644 index 00000000000..85e8a1c28cf --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-with-cur.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 1.00, + "bidfloorcur": "EUR", + "ext": { + "bidder": { + "publisherId": "12345" + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "bidfloor": 0.05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "publisherId": "12345" + } + }, + "id": "test-imp-id" + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-without-cur.json b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-without-cur.json new file mode 100644 index 00000000000..1417965741e --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-without-cur.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 1.00, + "ext": { + "bidder": { + "publisherId": "12345" + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "bidfloor": 1, + "ext": { + "bidder": { + "publisherId": "12345" + } + }, + "id": "test-imp-id" + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/currency-conversion.json b/adapters/rtbhouse/rtbhousetest/exemplary/currency-conversion.json deleted file mode 100644 index 95bfb1e4420..00000000000 --- a/adapters/rtbhouse/rtbhousetest/exemplary/currency-conversion.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "bidfloor": 1.00, - "bidfloorcur": "EUR", - "ext": { - "bidder": { - "placementId": "12345" - } - } - } - ], - "ext": { - "prebid": { - "currency": { - "rates": { - "EUR": { - "USD": 0.05 - } - }, - "usepbsrates": false - } - } - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-request-id", - "cur": ["USD"], - "imp": [ - { - "banner": { - "format": [ - { - "h": 250, - "w": 300 - } - ] - }, - "bidfloor": 0.05, - "bidfloorcur": "USD", - "ext": { - "bidder": { - "placementId": "12345" - } - }, - "id": "test-imp-id" - } - ], - "ext": { - "prebid": { - "currency": { - "rates": { - "EUR": { - "USD": 0.05 - } - }, - "usepbsrates": false - } - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id", - "cur": "USD", - "seatbid": [{ - "seat": "rtbhouse", - "bid": [{ - "id": "randomid", - "impid": "test-imp-id", - "price": 300, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300 - } - ] - }] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "randomid", - "impid": "test-imp-id", - "price": 300, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300 - }, - "type": "banner" - } - ] - } - ] - } diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/native-with-deprecated-native-prop.json b/adapters/rtbhouse/rtbhousetest/exemplary/native-with-deprecated-native-prop.json new file mode 100644 index 00000000000..e79b21a1207 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/native-with-deprecated-native-prop.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "bidder": {} + }, + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "id": "test-native-request", + "ext": {}, + "debug": 1 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-native-request", + "cur": [ + "USD" + ], + "imp": [ + { + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "ext": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-native-request", + "bidid": "test-bidid", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}}", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 4 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/native-with-proper-native-response.json b/adapters/rtbhouse/rtbhousetest/exemplary/native-with-proper-native-response.json new file mode 100644 index 00000000000..9f5962fd3a1 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/native-with-proper-native-response.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "bidder": {} + }, + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "id": "test-native-request", + "ext": {}, + "debug": 1 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-native-request", + "cur": [ + "USD" + ], + "imp": [ + { + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "ext": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-native-request", + "bidid": "test-bidid", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 4 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json b/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json index 2a8c4681ffa..468e73f2aca 100644 --- a/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json +++ b/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json @@ -56,7 +56,8 @@ "cid": "987", "crid": "12345678", "h": 250, - "w": 300 + "w": 300, + "mtype": 1 }] }], "cur": "USD" @@ -76,7 +77,8 @@ "cid": "987", "crid": "12345678", "w": 300, - "h": 250 + "h": 250, + "mtype": 1 }, "type": "banner" }] diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/two-bidfloors-given-param-and-impbidfloor.json b/adapters/rtbhouse/rtbhousetest/exemplary/two-bidfloors-given-param-and-impbidfloor.json new file mode 100644 index 00000000000..fc706d97004 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/two-bidfloors-given-param-and-impbidfloor.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 1.00, + "bidfloorcur": "EUR", + "ext": { + "bidder": { + "publisherId": "12345", + "bidfloor": 2.00 + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "bidfloor": 0.05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "publisherId": "12345", + "bidfloor": 2 + } + }, + "id": "test-imp-id" + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/banner-native-req-faulty-mtype-in-native.json b/adapters/rtbhouse/rtbhousetest/supplemental/banner-native-req-faulty-mtype-in-native.json new file mode 100644 index 00000000000..6e6f415acbc --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/banner-native-req-faulty-mtype-in-native.json @@ -0,0 +1,141 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "bidder": {} + }, + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + } + }, + { + "id": "test-banner-imp", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "id": "test-multi-slot-request", + "ext": {}, + "debug": 1 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-multi-slot-request", + "cur": [ + "USD" + ], + "imp": [ + { + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + }, + "ext": { + "bidder": {} + } + }, + { + "id": "test-banner-imp", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "ext": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-multi-slot-request", + "bidid": "test-bidid", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid-native", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}}", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 99 + }, + { + "id": "randomid-banner", + "impid": "test-banner-imp", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [{ + "bid": { + "id": "randomid-banner", + "impid": "test-banner-imp", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unrecognized bid type in response from rtbhouse for bid test-native-imp", + "comparison": "literal" + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-bidder-params.json b/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-bidder-params.json new file mode 100644 index 00000000000..70fddf620e6 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-bidder-params.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": 12345 + } + } + } + ], + "ext": {} + }, + "expectedMakeRequestsErrors": [ + { + "value": "Error while unmarshaling bidder extension", + "comparison": "literal" + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-no-impext.json b/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-no-impext.json new file mode 100644 index 00000000000..f416f8106fa --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-no-impext.json @@ -0,0 +1,25 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "ext": {} + }, + "expectedMakeRequestsErrors": [ + { + "value": "Bidder extension not provided or can't be unmarshalled", + "comparison": "literal" + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-native-prop.json b/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-native-prop.json new file mode 100644 index 00000000000..5e1aa0ef4b7 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-native-prop.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "bidder": {} + }, + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "id": "test-native-request", + "ext": {}, + "debug": 1 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-native-request", + "cur": [ + "USD" + ], + "imp": [ + { + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "ext": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-native-request", + "bidid": "test-bidid", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"native\":\"faulty object\"}", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 4 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unable to get native adm", + "comparison": "literal" + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-response.json b/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-response.json new file mode 100644 index 00000000000..41cc909f304 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-response.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "bidder": {} + }, + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "id": "test-native-request", + "ext": {}, + "debug": 1 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-native-request", + "cur": [ + "USD" + ], + "imp": [ + { + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "ext": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-native-request", + "bidid": "test-bidid", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"ver\":\"1.2\"", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 4 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unable to unmarshal native adm", + "comparison": "literal" + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-bad-mtype.json b/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-bad-mtype.json new file mode 100644 index 00000000000..fc52cc30601 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-bad-mtype.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 99 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unrecognized bid type in response from rtbhouse for bid test-imp-id", + "comparison": "literal" + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-no-mtype.json b/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-no-mtype.json new file mode 100644 index 00000000000..8bb9e8a1f89 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-no-mtype.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unrecognized bid type in response from rtbhouse for bid test-imp-id", + "comparison": "literal" + } + ] +} diff --git a/static/bidder-info/rtbhouse.yaml b/static/bidder-info/rtbhouse.yaml index ad2fbfcbc95..b80cc0ff4f8 100644 --- a/static/bidder-info/rtbhouse.yaml +++ b/static/bidder-info/rtbhouse.yaml @@ -7,6 +7,7 @@ capabilities: site: mediaTypes: - banner + - native userSync: # rtbhouse supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. From 8e9f3f359416289a8b988f5d2403daf68edc4593 Mon Sep 17 00:00:00 2001 From: SerhiiNahornyi Date: Mon, 30 Oct 2023 14:19:46 +0200 Subject: [PATCH 077/138] Rubicon: Add `imp[].ext.rp.rtb.formats` logic (#3255) --- adapters/rubicon/rubicon.go | 30 +- .../bidonmultiformat-with-formats-param.json | 323 ++++++++++++++++++ .../exemplary/bidonmultiformat.json | 12 + ...n-bidonmultiformat-with-formats-param.json | 187 ++++++++++ openrtb_ext/imp_rubicon.go | 1 + 5 files changed, 550 insertions(+), 3 deletions(-) create mode 100644 adapters/rubicon/rubicontest/exemplary/bidonmultiformat-with-formats-param.json create mode 100644 adapters/rubicon/rubicontest/exemplary/non-bidonmultiformat-with-formats-param.json diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 7d09224e8b4..431359c0b33 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -87,6 +87,11 @@ type rubiconImpExtRP struct { ZoneID int `json:"zone_id"` Target json.RawMessage `json:"target,omitempty"` Track rubiconImpExtRPTrack `json:"track"` + RTB *rubiconImpExtRpRtb `json:"rtb,omitempty"` +} + +type rubiconImpExtRpRtb struct { + Formats []openrtb_ext.BidType `json:"formats,omitempty"` } type rubiconUserExtRP struct { @@ -299,6 +304,10 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada Skadn: bidderExt.Skadn, } + if len(bidderExt.Bidder.Formats) > 0 { + impExt.RP.RTB = &rubiconImpExtRpRtb{bidderExt.Bidder.Formats} + } + imp.Ext, err = json.Marshal(&impExt) if err != nil { errs = append(errs, err) @@ -592,9 +601,19 @@ func prepareImpsToExtMap(impsToExtMap map[*openrtb2.Imp]rubiconExtImpBidder) map continue } - splitImps := splitMultiFormatImp(imp) + splitImps, formats := splitMultiFormatImp(imp) for _, imp := range splitImps { impCopy := imp + + existingFormats := bidderExt.Bidder.Formats + var resolvedFormats []openrtb_ext.BidType + if len(existingFormats) > 0 { + resolvedFormats = existingFormats + } else { + resolvedFormats = formats + } + bidderExt.Bidder.Formats = resolvedFormats + preparedImpsToExtMap[impCopy] = bidderExt } } @@ -602,9 +621,11 @@ func prepareImpsToExtMap(impsToExtMap map[*openrtb2.Imp]rubiconExtImpBidder) map return preparedImpsToExtMap } -func splitMultiFormatImp(imp *openrtb2.Imp) []*openrtb2.Imp { +func splitMultiFormatImp(imp *openrtb2.Imp) ([]*openrtb2.Imp, []openrtb_ext.BidType) { splitImps := make([]*openrtb2.Imp, 0) + mediaTypes := make([]openrtb_ext.BidType, 0) if imp.Banner != nil { + mediaTypes = append(mediaTypes, openrtb_ext.BidTypeBanner) impCopy := *imp impCopy.Video = nil impCopy.Native = nil @@ -613,6 +634,7 @@ func splitMultiFormatImp(imp *openrtb2.Imp) []*openrtb2.Imp { } if imp.Video != nil { + mediaTypes = append(mediaTypes, openrtb_ext.BidTypeVideo) impCopy := *imp impCopy.Banner = nil impCopy.Native = nil @@ -621,6 +643,7 @@ func splitMultiFormatImp(imp *openrtb2.Imp) []*openrtb2.Imp { } if imp.Native != nil { + mediaTypes = append(mediaTypes, openrtb_ext.BidTypeNative) impCopy := *imp impCopy.Banner = nil impCopy.Video = nil @@ -629,6 +652,7 @@ func splitMultiFormatImp(imp *openrtb2.Imp) []*openrtb2.Imp { } if imp.Audio != nil { + mediaTypes = append(mediaTypes, openrtb_ext.BidTypeAudio) impCopy := *imp impCopy.Banner = nil impCopy.Video = nil @@ -636,7 +660,7 @@ func splitMultiFormatImp(imp *openrtb2.Imp) []*openrtb2.Imp { splitImps = append(splitImps, &impCopy) } - return splitImps + return splitImps, mediaTypes } func resolveBidFloor(bidFloor float64, bidFloorCur string, reqInfo *adapters.ExtraRequestInfo) (float64, error) { diff --git a/adapters/rubicon/rubicontest/exemplary/bidonmultiformat-with-formats-param.json b/adapters/rubicon/rubicontest/exemplary/bidonmultiformat-with-formats-param.json new file mode 100644 index 00000000000..72efd23e298 --- /dev/null +++ b/adapters/rubicon/rubicontest/exemplary/bidonmultiformat-with-formats-param.json @@ -0,0 +1,323 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "site": { + "page": "somePage", + "ref": "someRef", + "search": "someSearch" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 400 + } + ] + }, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "bidonmultiformat": true, + "accountId": 1001, + "siteId": 113932, + "zoneId": 535510, + "formats": [ + "banner", + "video", + "native" + ] + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "uri?tk_xint=pbs-test-tracker", + "body": { + "id": "test-request-id", + "device": { + "ext": { + "rp": { + "pixelratio": 0 + } + }, + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "site": { + "page": "somePage", + "ref": "someRef", + "search": "someSearch", + "ext": { + "rp": { + "site_id": 113932 + } + }, + "publisher": { + "ext": { + "rp": { + "account_id": 1001 + } + } + } + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 400 + } + ], + "ext": { + "rp": { + "mime": "text/html" + } + } + }, + "ext": { + "rp": { + "rtb": { + "formats": [ + "banner", + "video", + "native" + ] + }, + "target": { + "page": [ + "somePage" + ], + "ref": [ + "someRef" + ], + "search": [ + "someSearch" + ] + }, + "track": { + "mint": "", + "mint_version": "" + }, + "zone_id": 535510 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adman" + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "uri?tk_xint=pbs-test-tracker", + "body": { + "id": "test-request-id", + "device": { + "ext": { + "rp": { + "pixelratio": 0 + } + }, + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "site": { + "page": "somePage", + "ref": "someRef", + "search": "someSearch", + "ext": { + "rp": { + "site_id": 113932 + } + }, + "publisher": { + "ext": { + "rp": { + "account_id": 1001 + } + } + } + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "secure": 1, + "video": { + "placement": 3, + "ext": { + "rp": { + "size_id": 203 + } + }, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "rp": { + "rtb": { + "formats": [ + "banner", + "video", + "native" + ] + }, + "target": { + "page": [ + "somePage" + ], + "ref": [ + "someRef" + ], + "search": [ + "someSearch" + ] + }, + "track": { + "mint": "", + "mint_version": "" + }, + "zone_id": 535510 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id-2", + "impid": "test-imp-id", + "price": 0.9, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adman" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id-2", + "impid": "test-imp-id", + "price": 0.9, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/rubicon/rubicontest/exemplary/bidonmultiformat.json b/adapters/rubicon/rubicontest/exemplary/bidonmultiformat.json index 71043771d1d..28b901bdb01 100644 --- a/adapters/rubicon/rubicontest/exemplary/bidonmultiformat.json +++ b/adapters/rubicon/rubicontest/exemplary/bidonmultiformat.json @@ -97,6 +97,12 @@ }, "ext": { "rp": { + "rtb": { + "formats": [ + "banner", + "video" + ] + }, "target": { "page": [ "somePage" @@ -203,6 +209,12 @@ }, "ext": { "rp": { + "rtb": { + "formats": [ + "banner", + "video" + ] + }, "target": { "page": [ "somePage" diff --git a/adapters/rubicon/rubicontest/exemplary/non-bidonmultiformat-with-formats-param.json b/adapters/rubicon/rubicontest/exemplary/non-bidonmultiformat-with-formats-param.json new file mode 100644 index 00000000000..a7d10421757 --- /dev/null +++ b/adapters/rubicon/rubicontest/exemplary/non-bidonmultiformat-with-formats-param.json @@ -0,0 +1,187 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "site": { + "page": "somePage", + "ref": "someRef", + "search": "someSearch" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 400 + } + ] + }, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "accountId": 1001, + "siteId": 113932, + "zoneId": 535510, + "formats": [ + "banner", + "video", + "native" + ] + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "uri?tk_xint=pbs-test-tracker", + "body": { + "id": "test-request-id", + "device": { + "ext": { + "rp": { + "pixelratio": 0 + } + }, + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "site": { + "page": "somePage", + "ref": "someRef", + "search": "someSearch", + "ext": { + "rp": { + "site_id": 113932 + } + }, + "publisher": { + "ext": { + "rp": { + "account_id": 1001 + } + } + } + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 400 + } + ], + "ext": { + "rp": { + "mime": "text/html" + } + } + }, + "ext": { + "rp": { + "rtb": { + "formats": [ + "banner", + "video", + "native" + ] + }, + "target": { + "page": [ + "somePage" + ], + "ref": [ + "someRef" + ], + "search": [ + "someSearch" + ] + }, + "track": { + "mint": "", + "mint_version": "" + }, + "zone_id": 535510 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adman" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/openrtb_ext/imp_rubicon.go b/openrtb_ext/imp_rubicon.go index 7264be1374c..7075b101637 100644 --- a/openrtb_ext/imp_rubicon.go +++ b/openrtb_ext/imp_rubicon.go @@ -16,6 +16,7 @@ type ExtImpRubicon struct { Video rubiconVideoParams `json:"video"` Debug impExtRubiconDebug `json:"debug,omitempty"` PChain string `json:"pchain,omitempty"` + Formats []BidType `json:"formats"` } // rubiconVideoParams defines the contract for bidrequest.imp[i].ext.prebid.bidder.rubicon.video From 3834c22f6c39035d529aede3a059fcff78489c38 Mon Sep 17 00:00:00 2001 From: Sonali-More-Xandr <87759626+Sonali-More-Xandr@users.noreply.github.com> Date: Mon, 30 Oct 2023 23:58:14 +0530 Subject: [PATCH 078/138] Revert "Rubicon: Add `imp[].ext.rp.rtb.formats` logic (#3255)" (#3268) This reverts commit 8e9f3f359416289a8b988f5d2403daf68edc4593. --- adapters/rubicon/rubicon.go | 30 +- .../bidonmultiformat-with-formats-param.json | 323 ------------------ .../exemplary/bidonmultiformat.json | 12 - ...n-bidonmultiformat-with-formats-param.json | 187 ---------- openrtb_ext/imp_rubicon.go | 1 - 5 files changed, 3 insertions(+), 550 deletions(-) delete mode 100644 adapters/rubicon/rubicontest/exemplary/bidonmultiformat-with-formats-param.json delete mode 100644 adapters/rubicon/rubicontest/exemplary/non-bidonmultiformat-with-formats-param.json diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 431359c0b33..7d09224e8b4 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -87,11 +87,6 @@ type rubiconImpExtRP struct { ZoneID int `json:"zone_id"` Target json.RawMessage `json:"target,omitempty"` Track rubiconImpExtRPTrack `json:"track"` - RTB *rubiconImpExtRpRtb `json:"rtb,omitempty"` -} - -type rubiconImpExtRpRtb struct { - Formats []openrtb_ext.BidType `json:"formats,omitempty"` } type rubiconUserExtRP struct { @@ -304,10 +299,6 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada Skadn: bidderExt.Skadn, } - if len(bidderExt.Bidder.Formats) > 0 { - impExt.RP.RTB = &rubiconImpExtRpRtb{bidderExt.Bidder.Formats} - } - imp.Ext, err = json.Marshal(&impExt) if err != nil { errs = append(errs, err) @@ -601,19 +592,9 @@ func prepareImpsToExtMap(impsToExtMap map[*openrtb2.Imp]rubiconExtImpBidder) map continue } - splitImps, formats := splitMultiFormatImp(imp) + splitImps := splitMultiFormatImp(imp) for _, imp := range splitImps { impCopy := imp - - existingFormats := bidderExt.Bidder.Formats - var resolvedFormats []openrtb_ext.BidType - if len(existingFormats) > 0 { - resolvedFormats = existingFormats - } else { - resolvedFormats = formats - } - bidderExt.Bidder.Formats = resolvedFormats - preparedImpsToExtMap[impCopy] = bidderExt } } @@ -621,11 +602,9 @@ func prepareImpsToExtMap(impsToExtMap map[*openrtb2.Imp]rubiconExtImpBidder) map return preparedImpsToExtMap } -func splitMultiFormatImp(imp *openrtb2.Imp) ([]*openrtb2.Imp, []openrtb_ext.BidType) { +func splitMultiFormatImp(imp *openrtb2.Imp) []*openrtb2.Imp { splitImps := make([]*openrtb2.Imp, 0) - mediaTypes := make([]openrtb_ext.BidType, 0) if imp.Banner != nil { - mediaTypes = append(mediaTypes, openrtb_ext.BidTypeBanner) impCopy := *imp impCopy.Video = nil impCopy.Native = nil @@ -634,7 +613,6 @@ func splitMultiFormatImp(imp *openrtb2.Imp) ([]*openrtb2.Imp, []openrtb_ext.BidT } if imp.Video != nil { - mediaTypes = append(mediaTypes, openrtb_ext.BidTypeVideo) impCopy := *imp impCopy.Banner = nil impCopy.Native = nil @@ -643,7 +621,6 @@ func splitMultiFormatImp(imp *openrtb2.Imp) ([]*openrtb2.Imp, []openrtb_ext.BidT } if imp.Native != nil { - mediaTypes = append(mediaTypes, openrtb_ext.BidTypeNative) impCopy := *imp impCopy.Banner = nil impCopy.Video = nil @@ -652,7 +629,6 @@ func splitMultiFormatImp(imp *openrtb2.Imp) ([]*openrtb2.Imp, []openrtb_ext.BidT } if imp.Audio != nil { - mediaTypes = append(mediaTypes, openrtb_ext.BidTypeAudio) impCopy := *imp impCopy.Banner = nil impCopy.Video = nil @@ -660,7 +636,7 @@ func splitMultiFormatImp(imp *openrtb2.Imp) ([]*openrtb2.Imp, []openrtb_ext.BidT splitImps = append(splitImps, &impCopy) } - return splitImps, mediaTypes + return splitImps } func resolveBidFloor(bidFloor float64, bidFloorCur string, reqInfo *adapters.ExtraRequestInfo) (float64, error) { diff --git a/adapters/rubicon/rubicontest/exemplary/bidonmultiformat-with-formats-param.json b/adapters/rubicon/rubicontest/exemplary/bidonmultiformat-with-formats-param.json deleted file mode 100644 index 72efd23e298..00000000000 --- a/adapters/rubicon/rubicontest/exemplary/bidonmultiformat-with-formats-param.json +++ /dev/null @@ -1,323 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch" - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "banner": { - "format": [ - { - "w": 300, - "h": 400 - } - ] - }, - "video": { - "placement": 3, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "bidder": { - "bidonmultiformat": true, - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510, - "formats": [ - "banner", - "video", - "native" - ] - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "uri?tk_xint=pbs-test-tracker", - "body": { - "id": "test-request-id", - "device": { - "ext": { - "rp": { - "pixelratio": 0 - } - }, - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - } - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "secure": 1, - "banner": { - "format": [ - { - "w": 300, - "h": 400 - } - ], - "ext": { - "rp": { - "mime": "text/html" - } - } - }, - "ext": { - "rp": { - "rtb": { - "formats": [ - "banner", - "video", - "native" - ] - }, - "target": { - "page": [ - "somePage" - ], - "ref": [ - "someRef" - ], - "search": [ - "someSearch" - ] - }, - "track": { - "mint": "", - "mint_version": "" - }, - "zone_id": 535510 - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "banner" - } - } - } - ], - "seat": "adman" - } - ], - "cur": "USD" - } - } - }, - { - "expectedRequest": { - "uri": "uri?tk_xint=pbs-test-tracker", - "body": { - "id": "test-request-id", - "device": { - "ext": { - "rp": { - "pixelratio": 0 - } - }, - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - } - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "secure": 1, - "video": { - "placement": 3, - "ext": { - "rp": { - "size_id": 203 - } - }, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "rp": { - "rtb": { - "formats": [ - "banner", - "video", - "native" - ] - }, - "target": { - "page": [ - "somePage" - ], - "ref": [ - "someRef" - ], - "search": [ - "someSearch" - ] - }, - "track": { - "mint": "", - "mint_version": "" - }, - "zone_id": 535510 - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id-2", - "impid": "test-imp-id", - "price": 0.9, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - } - ], - "seat": "adman" - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "banner" - } - } - }, - "type": "banner" - } - ] - }, - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test_bid_id-2", - "impid": "test-imp-id", - "price": 0.9, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/rubicon/rubicontest/exemplary/bidonmultiformat.json b/adapters/rubicon/rubicontest/exemplary/bidonmultiformat.json index 28b901bdb01..71043771d1d 100644 --- a/adapters/rubicon/rubicontest/exemplary/bidonmultiformat.json +++ b/adapters/rubicon/rubicontest/exemplary/bidonmultiformat.json @@ -97,12 +97,6 @@ }, "ext": { "rp": { - "rtb": { - "formats": [ - "banner", - "video" - ] - }, "target": { "page": [ "somePage" @@ -209,12 +203,6 @@ }, "ext": { "rp": { - "rtb": { - "formats": [ - "banner", - "video" - ] - }, "target": { "page": [ "somePage" diff --git a/adapters/rubicon/rubicontest/exemplary/non-bidonmultiformat-with-formats-param.json b/adapters/rubicon/rubicontest/exemplary/non-bidonmultiformat-with-formats-param.json deleted file mode 100644 index a7d10421757..00000000000 --- a/adapters/rubicon/rubicontest/exemplary/non-bidonmultiformat-with-formats-param.json +++ /dev/null @@ -1,187 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch" - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "banner": { - "format": [ - { - "w": 300, - "h": 400 - } - ] - }, - "video": { - "placement": 3, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "bidder": { - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510, - "formats": [ - "banner", - "video", - "native" - ] - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "uri?tk_xint=pbs-test-tracker", - "body": { - "id": "test-request-id", - "device": { - "ext": { - "rp": { - "pixelratio": 0 - } - }, - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - } - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "secure": 1, - "banner": { - "format": [ - { - "w": 300, - "h": 400 - } - ], - "ext": { - "rp": { - "mime": "text/html" - } - } - }, - "ext": { - "rp": { - "rtb": { - "formats": [ - "banner", - "video", - "native" - ] - }, - "target": { - "page": [ - "somePage" - ], - "ref": [ - "someRef" - ], - "search": [ - "someSearch" - ] - }, - "track": { - "mint": "", - "mint_version": "" - }, - "zone_id": 535510 - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "banner" - } - } - } - ], - "seat": "adman" - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "banner" - } - } - }, - "type": "banner" - } - ] - } - ] -} diff --git a/openrtb_ext/imp_rubicon.go b/openrtb_ext/imp_rubicon.go index 7075b101637..7264be1374c 100644 --- a/openrtb_ext/imp_rubicon.go +++ b/openrtb_ext/imp_rubicon.go @@ -16,7 +16,6 @@ type ExtImpRubicon struct { Video rubiconVideoParams `json:"video"` Debug impExtRubiconDebug `json:"debug,omitempty"` PChain string `json:"pchain,omitempty"` - Formats []BidType `json:"formats"` } // rubiconVideoParams defines the contract for bidrequest.imp[i].ext.prebid.bidder.rubicon.video From 72463be04b6e9a5b969839024f43899fd8be533f Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Mon, 30 Oct 2023 15:21:56 -0400 Subject: [PATCH 079/138] Revert "RTBHouse: native support (#3212)" (#3271) This reverts commit 2540dd884950f6c582d01263479f3e7973cda2eb. --- adapters/rtbhouse/rtbhouse.go | 65 +------- .../bidfloor-as-bidder-param-without-cur.json | 130 ---------------- .../exemplary/bidfloor-as-bidder-param.json | 133 ----------------- .../bidfloor-as-impbidfloor-with-cur.json | 130 ---------------- .../bidfloor-as-impbidfloor-without-cur.json | 128 ---------------- .../exemplary/currency-conversion.json | 123 +++++++++++++++ .../native-with-deprecated-native-prop.json | 100 ------------- .../native-with-proper-native-response.json | 100 ------------- .../rtbhousetest/exemplary/simple-banner.json | 6 +- ...bidfloors-given-param-and-impbidfloor.json | 132 ---------------- ...ner-native-req-faulty-mtype-in-native.json | 141 ------------------ .../faulty-request-bidder-params.json | 30 ---- .../faulty-request-no-impext.json | 25 ---- .../native-with-faulty-adm-native-prop.json | 90 ----------- .../native-with-faulty-adm-response.json | 90 ----------- .../supplemental/simple-banner-bad-mtype.json | 94 ------------ .../supplemental/simple-banner-no-mtype.json | 93 ------------ static/bidder-info/rtbhouse.yaml | 1 - 18 files changed, 130 insertions(+), 1481 deletions(-) delete mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param-without-cur.json delete mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param.json delete mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-with-cur.json delete mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-without-cur.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/currency-conversion.json delete mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/native-with-deprecated-native-prop.json delete mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/native-with-proper-native-response.json delete mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/two-bidfloors-given-param-and-impbidfloor.json delete mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/banner-native-req-faulty-mtype-in-native.json delete mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-bidder-params.json delete mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-no-impext.json delete mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-native-prop.json delete mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-response.json delete mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-bad-mtype.json delete mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-no-mtype.json diff --git a/adapters/rtbhouse/rtbhouse.go b/adapters/rtbhouse/rtbhouse.go index 92e0e57b287..089634cbd86 100644 --- a/adapters/rtbhouse/rtbhouse.go +++ b/adapters/rtbhouse/rtbhouse.go @@ -2,12 +2,10 @@ package rtbhouse import ( "encoding/json" - "errors" "fmt" "net/http" "strings" - "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/config" @@ -51,12 +49,9 @@ func (adapter *RTBHouseAdapter) MakeRequests( if err != nil { return nil, []error{err} } - if rtbhouseExt.BidFloor > 0 { + if rtbhouseExt.BidFloor > 0 && len(reqCopy.Cur) > 0 { + bidFloorCur = reqCopy.Cur[0] bidFloor = rtbhouseExt.BidFloor - bidFloorCur = BidderCurrency - if len(reqCopy.Cur) > 0 { - bidFloorCur = reqCopy.Cur[0] - } } } @@ -161,63 +156,13 @@ func (adapter *RTBHouseAdapter) MakeBids( for _, seatBid := range openRTBBidderResponse.SeatBid { for _, bid := range seatBid.Bid { bid := bid // pin! -> https://github.com/kyoh86/scopelint#whats-this - bidType, err := getMediaTypeForBid(bid) - if err != nil { - errs = append(errs, err) - continue - } else { - typedBid = &adapters.TypedBid{ - Bid: &bid, - BidType: bidType, - } - - // for native bid responses fix Adm field - if typedBid.BidType == openrtb_ext.BidTypeNative { - bid.AdM, err = getNativeAdm(bid.AdM) - if err != nil { - errs = append(errs, err) - continue - } - } - - bidderResponse.Bids = append(bidderResponse.Bids, typedBid) - } + typedBid = &adapters.TypedBid{Bid: &bid, BidType: "banner"} + bidderResponse.Bids = append(bidderResponse.Bids, typedBid) } } bidderResponse.Currency = BidderCurrency - return bidderResponse, errs - -} - -func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { - switch bid.MType { - case openrtb2.MarkupBanner: - return openrtb_ext.BidTypeBanner, nil - case openrtb2.MarkupNative: - return openrtb_ext.BidTypeNative, nil - default: - return "", fmt.Errorf("unrecognized bid type in response from rtbhouse for bid %s", bid.ImpID) - } -} - -func getNativeAdm(adm string) (string, error) { - nativeAdm := make(map[string]interface{}) - err := json.Unmarshal([]byte(adm), &nativeAdm) - if err != nil { - return adm, errors.New("unable to unmarshal native adm") - } - - // move bid.adm.native to bid.adm - if _, ok := nativeAdm["native"]; ok { - //using jsonparser to avoid marshaling, encode escape, etc. - value, dataType, _, err := jsonparser.Get([]byte(adm), string(openrtb_ext.BidTypeNative)) - if err != nil || dataType != jsonparser.Object { - return adm, errors.New("unable to get native adm") - } - adm = string(value) - } + return bidderResponse, nil - return adm, nil } diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param-without-cur.json b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param-without-cur.json deleted file mode 100644 index 79aa038c3ca..00000000000 --- a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param-without-cur.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "publisherId": "12345", - "bidfloor": 3.00 - } - } - } - ], - "ext": { - "prebid": { - "currency": { - "rates": { - "EUR": { - "USD": 0.05 - } - }, - "usepbsrates": false - } - } - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-request-id", - "cur": [ - "USD" - ], - "imp": [ - { - "banner": { - "format": [ - { - "h": 250, - "w": 300 - } - ] - }, - "bidfloor": 3.00, - "bidfloorcur": "USD", - "ext": { - "bidder": { - "publisherId": "12345", - "bidfloor": 3.00 - } - }, - "id": "test-imp-id" - } - ], - "ext": { - "prebid": { - "currency": { - "rates": { - "EUR": { - "USD": 0.05 - } - }, - "usepbsrates": false - } - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id", - "cur": "USD", - "seatbid": [ - { - "seat": "rtbhouse", - "bid": [ - { - "id": "randomid", - "impid": "test-imp-id", - "price": 300, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 1 - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "randomid", - "impid": "test-imp-id", - "price": 300, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 1 - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param.json b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param.json deleted file mode 100644 index 99b3a87ebfa..00000000000 --- a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param.json +++ /dev/null @@ -1,133 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "cur": [ - "EUR" - ], - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "publisherId": "12345", - "bidfloor": 2 - } - } - } - ], - "ext": { - "prebid": { - "currency": { - "rates": { - "EUR": { - "USD": 0.05 - } - }, - "usepbsrates": false - } - } - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-request-id", - "cur": [ - "USD" - ], - "imp": [ - { - "banner": { - "format": [ - { - "h": 250, - "w": 300 - } - ] - }, - "bidfloor": 0.1, - "bidfloorcur": "USD", - "ext": { - "bidder": { - "publisherId": "12345", - "bidfloor": 2 - } - }, - "id": "test-imp-id" - } - ], - "ext": { - "prebid": { - "currency": { - "rates": { - "EUR": { - "USD": 0.05 - } - }, - "usepbsrates": false - } - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id", - "cur": "USD", - "seatbid": [ - { - "seat": "rtbhouse", - "bid": [ - { - "id": "randomid", - "impid": "test-imp-id", - "price": 300, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 1 - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "randomid", - "impid": "test-imp-id", - "price": 300, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 1 - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-with-cur.json b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-with-cur.json deleted file mode 100644 index 85e8a1c28cf..00000000000 --- a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-with-cur.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "bidfloor": 1.00, - "bidfloorcur": "EUR", - "ext": { - "bidder": { - "publisherId": "12345" - } - } - } - ], - "ext": { - "prebid": { - "currency": { - "rates": { - "EUR": { - "USD": 0.05 - } - }, - "usepbsrates": false - } - } - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-request-id", - "cur": [ - "USD" - ], - "imp": [ - { - "banner": { - "format": [ - { - "h": 250, - "w": 300 - } - ] - }, - "bidfloor": 0.05, - "bidfloorcur": "USD", - "ext": { - "bidder": { - "publisherId": "12345" - } - }, - "id": "test-imp-id" - } - ], - "ext": { - "prebid": { - "currency": { - "rates": { - "EUR": { - "USD": 0.05 - } - }, - "usepbsrates": false - } - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id", - "cur": "USD", - "seatbid": [ - { - "seat": "rtbhouse", - "bid": [ - { - "id": "randomid", - "impid": "test-imp-id", - "price": 300, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 1 - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "randomid", - "impid": "test-imp-id", - "price": 300, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 1 - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-without-cur.json b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-without-cur.json deleted file mode 100644 index 1417965741e..00000000000 --- a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-without-cur.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "bidfloor": 1.00, - "ext": { - "bidder": { - "publisherId": "12345" - } - } - } - ], - "ext": { - "prebid": { - "currency": { - "rates": { - "EUR": { - "USD": 0.05 - } - }, - "usepbsrates": false - } - } - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-request-id", - "cur": [ - "USD" - ], - "imp": [ - { - "banner": { - "format": [ - { - "h": 250, - "w": 300 - } - ] - }, - "bidfloor": 1, - "ext": { - "bidder": { - "publisherId": "12345" - } - }, - "id": "test-imp-id" - } - ], - "ext": { - "prebid": { - "currency": { - "rates": { - "EUR": { - "USD": 0.05 - } - }, - "usepbsrates": false - } - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id", - "cur": "USD", - "seatbid": [ - { - "seat": "rtbhouse", - "bid": [ - { - "id": "randomid", - "impid": "test-imp-id", - "price": 300, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 1 - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "randomid", - "impid": "test-imp-id", - "price": 300, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 1 - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/currency-conversion.json b/adapters/rtbhouse/rtbhousetest/exemplary/currency-conversion.json new file mode 100644 index 00000000000..95bfb1e4420 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/currency-conversion.json @@ -0,0 +1,123 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 1.00, + "bidfloorcur": "EUR", + "ext": { + "bidder": { + "placementId": "12345" + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": ["USD"], + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "bidfloor": 0.05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "placementId": "12345" + } + }, + "id": "test-imp-id" + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "cur": "USD", + "seatbid": [{ + "seat": "rtbhouse", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + } + ] + }] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/native-with-deprecated-native-prop.json b/adapters/rtbhouse/rtbhousetest/exemplary/native-with-deprecated-native-prop.json deleted file mode 100644 index e79b21a1207..00000000000 --- a/adapters/rtbhouse/rtbhousetest/exemplary/native-with-deprecated-native-prop.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "bidder": {} - }, - "id": "test-native-imp", - "native": { - "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", - "ver": "1.2" - } - } - ], - "site": { - "page": "https://good.site/url" - }, - "id": "test-native-request", - "ext": {}, - "debug": 1 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-native-request", - "cur": [ - "USD" - ], - "imp": [ - { - "id": "test-native-imp", - "native": { - "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", - "ver": "1.2" - }, - "ext": { - "bidder": {} - } - } - ], - "site": { - "page": "https://good.site/url" - }, - "ext": {} - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-native-request", - "bidid": "test-bidid", - "cur": "USD", - "seatbid": [ - { - "seat": "rtbhouse", - "bid": [ - { - "id": "test-native-request", - "impid": "test-native-imp", - "price": 0.5, - "adid": "test-adid", - "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}}", - "adomain": [ "adomain.com" ], - "cid": "test-cid", - "crid": "test-crid", - "dealid": "test-dealid", - "mtype": 4 - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-native-request", - "impid": "test-native-imp", - "price": 0.5, - "adid": "test-adid", - "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}", - "adomain": [ "adomain.com" ], - "cid": "test-cid", - "crid": "test-crid", - "dealid": "test-dealid", - "mtype": 4 - }, - "type": "native" - } - ] - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/native-with-proper-native-response.json b/adapters/rtbhouse/rtbhousetest/exemplary/native-with-proper-native-response.json deleted file mode 100644 index 9f5962fd3a1..00000000000 --- a/adapters/rtbhouse/rtbhousetest/exemplary/native-with-proper-native-response.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "bidder": {} - }, - "id": "test-native-imp", - "native": { - "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", - "ver": "1.2" - } - } - ], - "site": { - "page": "https://good.site/url" - }, - "id": "test-native-request", - "ext": {}, - "debug": 1 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-native-request", - "cur": [ - "USD" - ], - "imp": [ - { - "id": "test-native-imp", - "native": { - "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", - "ver": "1.2" - }, - "ext": { - "bidder": {} - } - } - ], - "site": { - "page": "https://good.site/url" - }, - "ext": {} - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-native-request", - "bidid": "test-bidid", - "cur": "USD", - "seatbid": [ - { - "seat": "rtbhouse", - "bid": [ - { - "id": "test-native-request", - "impid": "test-native-imp", - "price": 0.5, - "adid": "test-adid", - "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}", - "adomain": [ "adomain.com" ], - "cid": "test-cid", - "crid": "test-crid", - "dealid": "test-dealid", - "mtype": 4 - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-native-request", - "impid": "test-native-imp", - "price": 0.5, - "adid": "test-adid", - "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}", - "adomain": [ "adomain.com" ], - "cid": "test-cid", - "crid": "test-crid", - "dealid": "test-dealid", - "mtype": 4 - }, - "type": "native" - } - ] - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json b/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json index 468e73f2aca..2a8c4681ffa 100644 --- a/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json +++ b/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json @@ -56,8 +56,7 @@ "cid": "987", "crid": "12345678", "h": 250, - "w": 300, - "mtype": 1 + "w": 300 }] }], "cur": "USD" @@ -77,8 +76,7 @@ "cid": "987", "crid": "12345678", "w": 300, - "h": 250, - "mtype": 1 + "h": 250 }, "type": "banner" }] diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/two-bidfloors-given-param-and-impbidfloor.json b/adapters/rtbhouse/rtbhousetest/exemplary/two-bidfloors-given-param-and-impbidfloor.json deleted file mode 100644 index fc706d97004..00000000000 --- a/adapters/rtbhouse/rtbhousetest/exemplary/two-bidfloors-given-param-and-impbidfloor.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "bidfloor": 1.00, - "bidfloorcur": "EUR", - "ext": { - "bidder": { - "publisherId": "12345", - "bidfloor": 2.00 - } - } - } - ], - "ext": { - "prebid": { - "currency": { - "rates": { - "EUR": { - "USD": 0.05 - } - }, - "usepbsrates": false - } - } - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-request-id", - "cur": [ - "USD" - ], - "imp": [ - { - "banner": { - "format": [ - { - "h": 250, - "w": 300 - } - ] - }, - "bidfloor": 0.05, - "bidfloorcur": "USD", - "ext": { - "bidder": { - "publisherId": "12345", - "bidfloor": 2 - } - }, - "id": "test-imp-id" - } - ], - "ext": { - "prebid": { - "currency": { - "rates": { - "EUR": { - "USD": 0.05 - } - }, - "usepbsrates": false - } - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-response-id", - "cur": "USD", - "seatbid": [ - { - "seat": "rtbhouse", - "bid": [ - { - "id": "randomid", - "impid": "test-imp-id", - "price": 300, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 1 - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "randomid", - "impid": "test-imp-id", - "price": 300, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 1 - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/banner-native-req-faulty-mtype-in-native.json b/adapters/rtbhouse/rtbhousetest/supplemental/banner-native-req-faulty-mtype-in-native.json deleted file mode 100644 index 6e6f415acbc..00000000000 --- a/adapters/rtbhouse/rtbhousetest/supplemental/banner-native-req-faulty-mtype-in-native.json +++ /dev/null @@ -1,141 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "bidder": {} - }, - "id": "test-native-imp", - "native": { - "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", - "ver": "1.2" - } - }, - { - "id": "test-banner-imp", - "banner": { - "format": [{ - "w": 300, - "h": 250 - }] - }, - "ext": { - "bidder": {} - } - } - ], - "site": { - "page": "https://good.site/url" - }, - "id": "test-multi-slot-request", - "ext": {}, - "debug": 1 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-multi-slot-request", - "cur": [ - "USD" - ], - "imp": [ - { - "id": "test-native-imp", - "native": { - "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", - "ver": "1.2" - }, - "ext": { - "bidder": {} - } - }, - { - "id": "test-banner-imp", - "banner": { - "format": [{ - "w": 300, - "h": 250 - }] - }, - "ext": { - "bidder": {} - } - } - ], - "site": { - "page": "https://good.site/url" - }, - "ext": {} - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-multi-slot-request", - "bidid": "test-bidid", - "cur": "USD", - "seatbid": [ - { - "seat": "rtbhouse", - "bid": [ - { - "id": "randomid-native", - "impid": "test-native-imp", - "price": 0.5, - "adid": "test-adid", - "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}}", - "adomain": [ "adomain.com" ], - "cid": "test-cid", - "crid": "test-crid", - "dealid": "test-dealid", - "mtype": 99 - }, - { - "id": "randomid-banner", - "impid": "test-banner-imp", - "price": 0.500000, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 1 - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [{ - "bid": { - "id": "randomid-banner", - "impid": "test-banner-imp", - "price": 0.500000, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 1 - }, - "type": "banner" - } - ] - } - ], - "expectedMakeBidsErrors": [ - { - "value": "unrecognized bid type in response from rtbhouse for bid test-native-imp", - "comparison": "literal" - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-bidder-params.json b/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-bidder-params.json deleted file mode 100644 index 70fddf620e6..00000000000 --- a/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-bidder-params.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "banner-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "publisherId": 12345 - } - } - } - ], - "ext": {} - }, - "expectedMakeRequestsErrors": [ - { - "value": "Error while unmarshaling bidder extension", - "comparison": "literal" - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-no-impext.json b/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-no-impext.json deleted file mode 100644 index f416f8106fa..00000000000 --- a/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-no-impext.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "banner-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - } - } - ], - "ext": {} - }, - "expectedMakeRequestsErrors": [ - { - "value": "Bidder extension not provided or can't be unmarshalled", - "comparison": "literal" - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-native-prop.json b/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-native-prop.json deleted file mode 100644 index 5e1aa0ef4b7..00000000000 --- a/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-native-prop.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "bidder": {} - }, - "id": "test-native-imp", - "native": { - "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", - "ver": "1.2" - } - } - ], - "site": { - "page": "https://good.site/url" - }, - "id": "test-native-request", - "ext": {}, - "debug": 1 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-native-request", - "cur": [ - "USD" - ], - "imp": [ - { - "id": "test-native-imp", - "native": { - "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", - "ver": "1.2" - }, - "ext": { - "bidder": {} - } - } - ], - "site": { - "page": "https://good.site/url" - }, - "ext": {} - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-native-request", - "bidid": "test-bidid", - "cur": "USD", - "seatbid": [ - { - "seat": "rtbhouse", - "bid": [ - { - "id": "test-native-request", - "impid": "test-native-imp", - "price": 0.5, - "adid": "test-adid", - "adm": "{\"native\":\"faulty object\"}", - "adomain": [ "adomain.com" ], - "cid": "test-cid", - "crid": "test-crid", - "dealid": "test-dealid", - "mtype": 4 - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [] - } - ], - "expectedMakeBidsErrors": [ - { - "value": "unable to get native adm", - "comparison": "literal" - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-response.json b/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-response.json deleted file mode 100644 index 41cc909f304..00000000000 --- a/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-response.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "mockBidRequest": { - "imp": [ - { - "ext": { - "bidder": {} - }, - "id": "test-native-imp", - "native": { - "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", - "ver": "1.2" - } - } - ], - "site": { - "page": "https://good.site/url" - }, - "id": "test-native-request", - "ext": {}, - "debug": 1 - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-native-request", - "cur": [ - "USD" - ], - "imp": [ - { - "id": "test-native-imp", - "native": { - "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", - "ver": "1.2" - }, - "ext": { - "bidder": {} - } - } - ], - "site": { - "page": "https://good.site/url" - }, - "ext": {} - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-native-request", - "bidid": "test-bidid", - "cur": "USD", - "seatbid": [ - { - "seat": "rtbhouse", - "bid": [ - { - "id": "test-native-request", - "impid": "test-native-imp", - "price": 0.5, - "adid": "test-adid", - "adm": "{\"ver\":\"1.2\"", - "adomain": [ "adomain.com" ], - "cid": "test-cid", - "crid": "test-crid", - "dealid": "test-dealid", - "mtype": 4 - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [] - } - ], - "expectedMakeBidsErrors": [ - { - "value": "unable to unmarshal native adm", - "comparison": "literal" - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-bad-mtype.json b/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-bad-mtype.json deleted file mode 100644 index fc52cc30601..00000000000 --- a/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-bad-mtype.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "page": "https://good.site/url" - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": {} - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-request-id", - "cur": [ - "USD" - ], - "site": { - "page": "https://good.site/url" - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": {} - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "rtbhouse", - "bid": [ - { - "id": "randomid", - "impid": "test-imp-id", - "price": 0.500000, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300, - "mtype": 99 - } - ] - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [] - } - ], - "expectedMakeBidsErrors": [ - { - "value": "unrecognized bid type in response from rtbhouse for bid test-imp-id", - "comparison": "literal" - } - ] -} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-no-mtype.json b/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-no-mtype.json deleted file mode 100644 index 8bb9e8a1f89..00000000000 --- a/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-no-mtype.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "page": "https://good.site/url" - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": {} - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid_server", - "body": { - "id": "test-request-id", - "cur": [ - "USD" - ], - "site": { - "page": "https://good.site/url" - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": {} - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "rtbhouse", - "bid": [ - { - "id": "randomid", - "impid": "test-imp-id", - "price": 0.500000, - "adid": "12345678", - "adm": "some-test-ad", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300 - } - ] - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [] - } - ], - "expectedMakeBidsErrors": [ - { - "value": "unrecognized bid type in response from rtbhouse for bid test-imp-id", - "comparison": "literal" - } - ] -} diff --git a/static/bidder-info/rtbhouse.yaml b/static/bidder-info/rtbhouse.yaml index b80cc0ff4f8..ad2fbfcbc95 100644 --- a/static/bidder-info/rtbhouse.yaml +++ b/static/bidder-info/rtbhouse.yaml @@ -7,7 +7,6 @@ capabilities: site: mediaTypes: - banner - - native userSync: # rtbhouse supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. From b200357f116b7c5ece866bf21f5c62e002787b0a Mon Sep 17 00:00:00 2001 From: guscarreon Date: Tue, 31 Oct 2023 11:57:02 -0400 Subject: [PATCH 080/138] Increment to V2 in Dockerfile and Makefile (#3272) --- Dockerfile | 2 +- Makefile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 090d9a79954..bff68b614b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ RUN go mod tidy RUN go mod vendor ARG TEST="true" RUN if [ "$TEST" != "false" ]; then ./validate.sh ; fi -RUN go build -mod=vendor -ldflags "-X github.com/prebid/prebid-server/version.Ver=`git describe --tags | sed 's/^v//'` -X github.com/prebid/prebid-server/version.Rev=`git rev-parse HEAD`" . +RUN go build -mod=vendor -ldflags "-X github.com/prebid/prebid-server/v2/version.Ver=`git describe --tags | sed 's/^v//'` -X github.com/prebid/prebid-server/v2/version.Rev=`git rev-parse HEAD`" . FROM ubuntu:20.04 AS release LABEL maintainer="hans.hjort@xandr.com" diff --git a/Makefile b/Makefile index b5b7281e5ed..2d8aae6c78a 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ test: deps ifeq "$(adapter)" "" ./validate.sh else - go test github.com/prebid/prebid-server/adapters/$(adapter) -bench=. + go test github.com/prebid/prebid-server/v2/adapters/$(adapter) -bench=. endif # build-modules generates modules/builder.go file which provides a list of all available modules @@ -32,4 +32,4 @@ image: # format runs format format: - ./scripts/format.sh -f true \ No newline at end of file + ./scripts/format.sh -f true From 5941d46efe3a59015750e56d293838b44c309701 Mon Sep 17 00:00:00 2001 From: ashishshinde-pubm <109787960+ashishshinde-pubm@users.noreply.github.com> Date: Mon, 13 Nov 2023 23:36:39 +0530 Subject: [PATCH 081/138] Fix: BidderInfo OpenRTB field data lost on start up (#3178) --- config/bidderinfo.go | 3 +++ config/bidderinfo_test.go | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/config/bidderinfo.go b/config/bidderinfo.go index 45abc85227a..e4015b8e00b 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -605,6 +605,9 @@ func applyBidderInfoConfigOverrides(configBidderInfos BidderInfos, fsBidderInfos if bidderInfo.EndpointCompression == "" && fsBidderCfg.EndpointCompression != "" { bidderInfo.EndpointCompression = fsBidderCfg.EndpointCompression } + if bidderInfo.OpenRTB == nil && fsBidderCfg.OpenRTB != nil { + bidderInfo.OpenRTB = fsBidderCfg.OpenRTB + } fsBidderInfos[string(normalizedBidderName)] = bidderInfo } else { diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go index ad242bd0a47..afb48c2fe81 100644 --- a/config/bidderinfo_test.go +++ b/config/bidderinfo_test.go @@ -1649,6 +1649,18 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { givenConfigBidderInfos: BidderInfos{"a": {EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}, expectedBidderInfos: BidderInfos{"a": {EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}, }, + { + description: "Don't override OpenRTB", + givenFsBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "v1", GPPSupported: true}}}, + givenConfigBidderInfos: BidderInfos{"a": {}}, + expectedBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "v1", GPPSupported: true}}}, + }, + { + description: "Override OpenRTB", + givenFsBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "v1", GPPSupported: true}}}, + givenConfigBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "v2", GPPSupported: false}}}, + expectedBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "v2", GPPSupported: false}}}, + }, } for _, test := range testCases { bidderInfos, resultErr := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) From 909926477ee48c0f2dffbbe2663649e4c586c75c Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Mon, 13 Nov 2023 10:24:34 -0800 Subject: [PATCH 082/138] Fix for null string unmarshal error (#3284) --- exchange/bidder.go | 4 ++-- exchange/bidder_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index db654bf47ae..610122fbbf2 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -406,7 +406,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeResponse.Response, []error) { var errs []error - var nativeMarkup *nativeResponse.Response + var nativeMarkup nativeResponse.Response if err := jsonutil.UnmarshalValid(json.RawMessage(bid.AdM), &nativeMarkup); err != nil || len(nativeMarkup.Assets) == 0 { // Some bidders are returning non-IAB compliant native markup. In this case Prebid server will not be able to add types. E.g Facebook return nil, errs @@ -429,7 +429,7 @@ func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeRes } } - return nativeMarkup, errs + return &nativeMarkup, errs } func setAssetTypes(asset nativeResponse.Asset, nativePayload nativeRequests.Request) error { diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 824aecc8583..6db61d0d883 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -1568,6 +1568,46 @@ func TestMobileNativeTypes(t *testing.T) { } } +func TestAddNativeTypes(t *testing.T) { + testCases := []struct { + description string + bidderRequest *openrtb2.BidRequest + bid *openrtb2.Bid + expectedResponse *nativeResponse.Response + expectedErrors []error + }{ + { + description: "Null in bid.Adm in response", + bidderRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "some-imp-id", + Native: &openrtb2.Native{ + Request: "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}}]}", + }, + }, + }, + App: &openrtb2.App{}, + }, + bid: &openrtb2.Bid{ + ImpID: "some-imp-id", + AdM: "null", + Price: 10, + }, + expectedResponse: nil, + expectedErrors: nil, + }, + } + + for _, tt := range testCases { + t.Run(tt.description, func(t *testing.T) { + resp, errs := addNativeTypes(tt.bid, tt.bidderRequest) + assert.Equal(t, tt.expectedResponse, resp, "response") + assert.Equal(t, tt.expectedErrors, errs, "errors") + }) + } +} + func TestRequestBidsStoredBidResponses(t *testing.T) { respBody := "{\"bid\":false}" respStatus := 200 From f52dbb192288d677044733f1f12b0e9c12e9ed50 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Mon, 13 Nov 2023 23:51:25 -0500 Subject: [PATCH 083/138] Fix: use bidder info fs config as base when merging overrides (#3289) --- config/bidderinfo.go | 116 ++++++++++++++++------------ config/bidderinfo_test.go | 155 +++++++++++++++++++++++++++----------- config/config.go | 36 ++++++++- config/config_test.go | 150 ++++++++++++++++++++++++++++++++++++ 4 files changed, 362 insertions(+), 95 deletions(-) diff --git a/config/bidderinfo.go b/config/bidderinfo.go index e4015b8e00b..d6d0ba79ba0 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -560,61 +560,79 @@ func validateSyncer(bidderInfo BidderInfo) error { return nil } -func applyBidderInfoConfigOverrides(configBidderInfos BidderInfos, fsBidderInfos BidderInfos, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) { - for bidderName, bidderInfo := range configBidderInfos { - normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName) - if !bidderNameExists { +func applyBidderInfoConfigOverrides(configBidderInfos nillableFieldBidderInfos, fsBidderInfos BidderInfos, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) { + mergedBidderInfos := make(map[string]BidderInfo, len(fsBidderInfos)) + + for bidderName, configBidderInfo := range configBidderInfos { + normalizedBidderName, exists := normalizeBidderName(bidderName) + if !exists { return nil, fmt.Errorf("error setting configuration for bidder %s: unknown bidder", bidderName) } - if fsBidderCfg, exists := fsBidderInfos[string(normalizedBidderName)]; exists { - bidderInfo.Syncer = bidderInfo.Syncer.Override(fsBidderCfg.Syncer) + fsBidderInfo, exists := fsBidderInfos[string(normalizedBidderName)] + if !exists { + return nil, fmt.Errorf("error finding configuration for bidder %s: unknown bidder", bidderName) + } - if bidderInfo.Endpoint == "" && len(fsBidderCfg.Endpoint) > 0 { - bidderInfo.Endpoint = fsBidderCfg.Endpoint - } - if bidderInfo.ExtraAdapterInfo == "" && len(fsBidderCfg.ExtraAdapterInfo) > 0 { - bidderInfo.ExtraAdapterInfo = fsBidderCfg.ExtraAdapterInfo - } - if bidderInfo.Maintainer == nil && fsBidderCfg.Maintainer != nil { - bidderInfo.Maintainer = fsBidderCfg.Maintainer - } - if bidderInfo.Capabilities == nil && fsBidderCfg.Capabilities != nil { - bidderInfo.Capabilities = fsBidderCfg.Capabilities - } - if bidderInfo.Debug == nil && fsBidderCfg.Debug != nil { - bidderInfo.Debug = fsBidderCfg.Debug - } - if bidderInfo.GVLVendorID == 0 && fsBidderCfg.GVLVendorID > 0 { - bidderInfo.GVLVendorID = fsBidderCfg.GVLVendorID - } - if bidderInfo.XAPI.Username == "" && fsBidderCfg.XAPI.Username != "" { - bidderInfo.XAPI.Username = fsBidderCfg.XAPI.Username - } - if bidderInfo.XAPI.Password == "" && fsBidderCfg.XAPI.Password != "" { - bidderInfo.XAPI.Password = fsBidderCfg.XAPI.Password - } - if bidderInfo.XAPI.Tracker == "" && fsBidderCfg.XAPI.Tracker != "" { - bidderInfo.XAPI.Tracker = fsBidderCfg.XAPI.Tracker - } - if bidderInfo.PlatformID == "" && fsBidderCfg.PlatformID != "" { - bidderInfo.PlatformID = fsBidderCfg.PlatformID - } - if bidderInfo.AppSecret == "" && fsBidderCfg.AppSecret != "" { - bidderInfo.AppSecret = fsBidderCfg.AppSecret - } - if bidderInfo.EndpointCompression == "" && fsBidderCfg.EndpointCompression != "" { - bidderInfo.EndpointCompression = fsBidderCfg.EndpointCompression - } - if bidderInfo.OpenRTB == nil && fsBidderCfg.OpenRTB != nil { - bidderInfo.OpenRTB = fsBidderCfg.OpenRTB - } + mergedBidderInfo := fsBidderInfo + mergedBidderInfo.Syncer = configBidderInfo.bidderInfo.Syncer.Override(fsBidderInfo.Syncer) + if len(configBidderInfo.bidderInfo.Endpoint) > 0 { + mergedBidderInfo.Endpoint = configBidderInfo.bidderInfo.Endpoint + } + if len(configBidderInfo.bidderInfo.ExtraAdapterInfo) > 0 { + mergedBidderInfo.ExtraAdapterInfo = configBidderInfo.bidderInfo.ExtraAdapterInfo + } + if configBidderInfo.bidderInfo.Maintainer != nil { + mergedBidderInfo.Maintainer = configBidderInfo.bidderInfo.Maintainer + } + if configBidderInfo.bidderInfo.Capabilities != nil { + mergedBidderInfo.Capabilities = configBidderInfo.bidderInfo.Capabilities + } + if configBidderInfo.bidderInfo.Debug != nil { + mergedBidderInfo.Debug = configBidderInfo.bidderInfo.Debug + } + if configBidderInfo.bidderInfo.GVLVendorID > 0 { + mergedBidderInfo.GVLVendorID = configBidderInfo.bidderInfo.GVLVendorID + } + if configBidderInfo.bidderInfo.XAPI.Username != "" { + mergedBidderInfo.XAPI.Username = configBidderInfo.bidderInfo.XAPI.Username + } + if configBidderInfo.bidderInfo.XAPI.Password != "" { + mergedBidderInfo.XAPI.Password = configBidderInfo.bidderInfo.XAPI.Password + } + if configBidderInfo.bidderInfo.XAPI.Tracker != "" { + mergedBidderInfo.XAPI.Tracker = configBidderInfo.bidderInfo.XAPI.Tracker + } + if configBidderInfo.bidderInfo.PlatformID != "" { + mergedBidderInfo.PlatformID = configBidderInfo.bidderInfo.PlatformID + } + if configBidderInfo.bidderInfo.AppSecret != "" { + mergedBidderInfo.AppSecret = configBidderInfo.bidderInfo.AppSecret + } + if configBidderInfo.nillableFields.Disabled != nil { + mergedBidderInfo.Disabled = configBidderInfo.bidderInfo.Disabled + } + if configBidderInfo.nillableFields.ModifyingVastXmlAllowed != nil { + mergedBidderInfo.ModifyingVastXmlAllowed = configBidderInfo.bidderInfo.ModifyingVastXmlAllowed + } + if configBidderInfo.bidderInfo.Experiment.AdsCert.Enabled == true { + mergedBidderInfo.Experiment.AdsCert.Enabled = true + } + if configBidderInfo.bidderInfo.EndpointCompression != "" { + mergedBidderInfo.EndpointCompression = configBidderInfo.bidderInfo.EndpointCompression + } + if configBidderInfo.bidderInfo.OpenRTB != nil { + mergedBidderInfo.OpenRTB = configBidderInfo.bidderInfo.OpenRTB + } - fsBidderInfos[string(normalizedBidderName)] = bidderInfo - } else { - return nil, fmt.Errorf("error finding configuration for bidder %s: unknown bidder", bidderName) + mergedBidderInfos[string(normalizedBidderName)] = mergedBidderInfo + } + for bidderName, fsBidderInfo := range fsBidderInfos { + if _, exists := mergedBidderInfos[bidderName]; !exists { + mergedBidderInfos[bidderName] = fsBidderInfo } } - return fsBidderInfos, nil + + return mergedBidderInfos, nil } // Override returns a new Syncer object where values in the original are replaced by non-empty/non-default diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go index afb48c2fe81..ee1b35d4994 100644 --- a/config/bidderinfo_test.go +++ b/config/bidderinfo_test.go @@ -1490,8 +1490,12 @@ func TestSyncerEndpointOverride(t *testing.T) { func TestApplyBidderInfoConfigSyncerOverrides(t *testing.T) { var ( givenFileSystem = BidderInfos{"a": {Syncer: &Syncer{Key: "original"}}} - givenConfig = BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}} - expected = BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}} + givenConfig = nillableFieldBidderInfos{ + "a": { + bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}, + }, + } + expected = BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}} ) result, resultErr := applyBidderInfoConfigOverrides(givenConfig, givenFileSystem, mockNormalizeBidderName) @@ -1500,47 +1504,49 @@ func TestApplyBidderInfoConfigSyncerOverrides(t *testing.T) { } func TestApplyBidderInfoConfigOverrides(t *testing.T) { + falseValue := false + var testCases = []struct { description string givenFsBidderInfos BidderInfos - givenConfigBidderInfos BidderInfos + givenConfigBidderInfos nillableFieldBidderInfos expectedError string expectedBidderInfos BidderInfos }{ { description: "Don't override endpoint", givenFsBidderInfos: BidderInfos{"a": {Endpoint: "original"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Endpoint: "original", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override endpoint", givenFsBidderInfos: BidderInfos{"a": {Endpoint: "original"}}, - givenConfigBidderInfos: BidderInfos{"a": {Endpoint: "override", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Endpoint: "override", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Endpoint: "override", Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override ExtraAdapterInfo", givenFsBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "original"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "original", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override ExtraAdapterInfo", givenFsBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "original"}}, - givenConfigBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "override", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ExtraAdapterInfo: "override", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "override", Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override Maintainer", givenFsBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "original"}}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "original"}, Syncer: &Syncer{Key: "override"}}}, }, { description: "Override maintainer", givenFsBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "original"}}}, - givenConfigBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "override"}, Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Maintainer: &MaintainerInfo{Email: "override"}, Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "override"}, Syncer: &Syncer{Key: "override"}}}, }, { @@ -1548,7 +1554,7 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { givenFsBidderInfos: BidderInfos{"a": { Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}}, }}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": { Syncer: &Syncer{Key: "override"}, Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}}, @@ -1559,10 +1565,10 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { givenFsBidderInfos: BidderInfos{"a": { Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}}, }}, - givenConfigBidderInfos: BidderInfos{"a": { + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ Syncer: &Syncer{Key: "override"}, Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}}, - }}, + }}}, expectedBidderInfos: BidderInfos{"a": { Syncer: &Syncer{Key: "override"}, Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}}, @@ -1571,25 +1577,25 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { { description: "Don't override Debug", givenFsBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: true}}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: true}, Syncer: &Syncer{Key: "override"}}}, }, { description: "Override Debug", givenFsBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: true}}}, - givenConfigBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: false}, Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Debug: &DebugInfo{Allow: false}, Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: false}, Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override GVLVendorID", givenFsBidderInfos: BidderInfos{"a": {GVLVendorID: 5}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}, }, { description: "Override GVLVendorID", givenFsBidderInfos: BidderInfos{"a": {}}, - givenConfigBidderInfos: BidderInfos{"a": {GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}, }, { @@ -1597,7 +1603,7 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { givenFsBidderInfos: BidderInfos{"a": { XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, }}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": { XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, Syncer: &Syncer{Key: "override"}}}, @@ -1606,9 +1612,9 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { description: "Override XAPI", givenFsBidderInfos: BidderInfos{"a": { XAPI: AdapterXAPI{Username: "username", Password: "password", Tracker: "tracker"}}}, - givenConfigBidderInfos: BidderInfos{"a": { + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, - Syncer: &Syncer{Key: "override"}}}, + Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": { XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, Syncer: &Syncer{Key: "override"}}}, @@ -1616,50 +1622,92 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { { description: "Don't override PlatformID", givenFsBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override PlatformID", givenFsBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID1"}}, - givenConfigBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID2", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{PlatformID: "PlatformID2", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID2", Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override AppSecret", givenFsBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override AppSecret", givenFsBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret1"}}, - givenConfigBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret2", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{AppSecret: "AppSecret2", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret2", Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override EndpointCompression", givenFsBidderInfos: BidderInfos{"a": {EndpointCompression: "GZIP"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {EndpointCompression: "GZIP", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override EndpointCompression", givenFsBidderInfos: BidderInfos{"a": {EndpointCompression: "GZIP"}}, - givenConfigBidderInfos: BidderInfos{"a": {EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}, }, + { + description: "Don't override Disabled", + givenFsBidderInfos: BidderInfos{"a": {Disabled: true}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Disabled: false, Syncer: &Syncer{Key: "override"}}, nillableFields: bidderInfoNillableFields{Disabled: nil}}}, + expectedBidderInfos: BidderInfos{"a": {Disabled: true, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override Disabled", + givenFsBidderInfos: BidderInfos{"a": {Disabled: true}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Disabled: false, Syncer: &Syncer{Key: "override"}}, nillableFields: bidderInfoNillableFields{Disabled: &falseValue}}}, + expectedBidderInfos: BidderInfos{"a": {Disabled: false, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override ModifyingVastXmlAllowed", + givenFsBidderInfos: BidderInfos{"a": {ModifyingVastXmlAllowed: true}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ModifyingVastXmlAllowed: false, Syncer: &Syncer{Key: "override"}}, nillableFields: bidderInfoNillableFields{ModifyingVastXmlAllowed: nil}}}, + expectedBidderInfos: BidderInfos{"a": {ModifyingVastXmlAllowed: true, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override ModifyingVastXmlAllowed", + givenFsBidderInfos: BidderInfos{"a": {ModifyingVastXmlAllowed: true}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ModifyingVastXmlAllowed: false, Syncer: &Syncer{Key: "override"}}, nillableFields: bidderInfoNillableFields{ModifyingVastXmlAllowed: &falseValue}}}, + expectedBidderInfos: BidderInfos{"a": {ModifyingVastXmlAllowed: false, Syncer: &Syncer{Key: "override"}}}, + }, { description: "Don't override OpenRTB", - givenFsBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "v1", GPPSupported: true}}}, - givenConfigBidderInfos: BidderInfos{"a": {}}, - expectedBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "v1", GPPSupported: true}}}, + givenFsBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "1"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, + expectedBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "1"}, Syncer: &Syncer{Key: "override"}}}, }, { description: "Override OpenRTB", - givenFsBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "v1", GPPSupported: true}}}, - givenConfigBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "v2", GPPSupported: false}}}, - expectedBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "v2", GPPSupported: false}}}, + givenFsBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "1"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{OpenRTB: &OpenRTBInfo{Version: "2"}, Syncer: &Syncer{Key: "override"}}}}, + expectedBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "2"}, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override AliasOf", + givenFsBidderInfos: BidderInfos{"a": {AliasOf: "Alias1"}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{}}}, + expectedBidderInfos: BidderInfos{"a": {AliasOf: "Alias1"}}, + }, + { + description: "Attempt override AliasOf but ignored", + givenFsBidderInfos: BidderInfos{"a": {AliasOf: "Alias1"}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{AliasOf: "Alias2"}}}, + expectedBidderInfos: BidderInfos{"a": {AliasOf: "Alias1"}}, + }, + { + description: "Two bidder infos: One with overrides and one without", + givenFsBidderInfos: BidderInfos{"a": {Endpoint: "original"}, "b": {Endpoint: "b endpoint"}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Endpoint: "override", Syncer: &Syncer{Key: "override"}}}}, + expectedBidderInfos: BidderInfos{"a": {Endpoint: "override", Syncer: &Syncer{Key: "override"}}, "b": {Endpoint: "b endpoint"}}, }, } for _, test := range testCases { @@ -1671,26 +1719,31 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { func TestApplyBidderInfoConfigOverridesInvalid(t *testing.T) { var testCases = []struct { - description string - givenFsBidderInfos BidderInfos - givenConfigBidderInfos BidderInfos - expectedError string - expectedBidderInfos BidderInfos + description string + givenFsBidderInfos BidderInfos + givenNillableFieldBidderInfos nillableFieldBidderInfos + expectedError string + expectedBidderInfos BidderInfos }{ { - description: "Bidder doesn't exists in bidder list", - givenConfigBidderInfos: BidderInfos{"unknown": {Syncer: &Syncer{Key: "override"}}}, - expectedError: "error setting configuration for bidder unknown: unknown bidder", + description: "Bidder doesn't exists in bidder list", + givenNillableFieldBidderInfos: nillableFieldBidderInfos{"unknown": nillableFieldBidderInfo{ + bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}, + }}, + expectedError: "error setting configuration for bidder unknown: unknown bidder", }, { - description: "Bidder doesn't exists in file system", - givenFsBidderInfos: BidderInfos{"unknown": {Endpoint: "original"}}, - givenConfigBidderInfos: BidderInfos{"bidderA": {Syncer: &Syncer{Key: "override"}}}, - expectedError: "error finding configuration for bidder bidderA: unknown bidder", + description: "Bidder doesn't exists in file system", + givenFsBidderInfos: BidderInfos{"unknown": {Endpoint: "original"}}, + givenNillableFieldBidderInfos: nillableFieldBidderInfos{"bidderA": nillableFieldBidderInfo{ + bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}, + }}, + expectedError: "error finding configuration for bidder bidderA: unknown bidder", }, } for _, test := range testCases { - _, err := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) + + _, err := applyBidderInfoConfigOverrides(test.givenNillableFieldBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) assert.ErrorContains(t, err, test.expectedError, test.description+":err") } } @@ -1702,7 +1755,19 @@ func TestReadFullYamlBidderConfig(t *testing.T) { err := yaml.Unmarshal([]byte(fullBidderYAMLConfig), &bidderInf) require.NoError(t, err) - actualBidderInfo, err := applyBidderInfoConfigOverrides(BidderInfos{bidder: bidderInf}, BidderInfos{bidder: {Syncer: &Syncer{Supports: []string{"iframe"}}}}, mockNormalizeBidderName) + bidderInfoOverrides := nillableFieldBidderInfos{ + bidder: nillableFieldBidderInfo{ + bidderInfo: bidderInf, + nillableFields: bidderInfoNillableFields{ + Disabled: &bidderInf.Disabled, + ModifyingVastXmlAllowed: &bidderInf.ModifyingVastXmlAllowed, + }, + }, + } + bidderInfoBase := BidderInfos{ + bidder: {Syncer: &Syncer{Supports: []string{"iframe"}}}, + } + actualBidderInfo, err := applyBidderInfoConfigOverrides(bidderInfoOverrides, bidderInfoBase, mockNormalizeBidderName) require.NoError(t, err) expectedBidderInfo := BidderInfos{ diff --git a/config/config.go b/config/config.go index 11b7eb5841e..34c95822b3d 100644 --- a/config/config.go +++ b/config/config.go @@ -758,7 +758,11 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin // Migrate combo stored request config to separate stored_reqs and amp stored_reqs configs. resolvedStoredRequestsConfig(&c) - mergedBidderInfos, err := applyBidderInfoConfigOverrides(c.BidderInfos, bidderInfos, normalizeBidderName) + configBidderInfosWithNillableFields, err := setConfigBidderInfoNillableFields(v, c.BidderInfos) + if err != nil { + return nil, err + } + mergedBidderInfos, err := applyBidderInfoConfigOverrides(configBidderInfosWithNillableFields, bidderInfos, normalizeBidderName) if err != nil { return nil, err } @@ -773,6 +777,36 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin return &c, nil } +type bidderInfoNillableFields struct { + Disabled *bool `yaml:"disabled" mapstructure:"disabled"` + ModifyingVastXmlAllowed *bool `yaml:"modifyingVastXmlAllowed" mapstructure:"modifyingVastXmlAllowed"` +} +type nillableFieldBidderInfos map[string]nillableFieldBidderInfo +type nillableFieldBidderInfo struct { + nillableFields bidderInfoNillableFields + bidderInfo BidderInfo +} + +func setConfigBidderInfoNillableFields(v *viper.Viper, bidderInfos BidderInfos) (nillableFieldBidderInfos, error) { + if len(bidderInfos) == 0 || v == nil { + return nil, nil + } + infos := make(nillableFieldBidderInfos, len(bidderInfos)) + + for bidderName, bidderInfo := range bidderInfos { + info := nillableFieldBidderInfo{bidderInfo: bidderInfo} + + if err := v.UnmarshalKey("adapters."+bidderName+".disabled", &info.nillableFields.Disabled); err != nil { + return nil, fmt.Errorf("viper failed to unmarshal bidder config disabled: %v", err) + } + if err := v.UnmarshalKey("adapters."+bidderName+".modifyingvastxmlallowed", &info.nillableFields.ModifyingVastXmlAllowed); err != nil { + return nil, fmt.Errorf("viper failed to unmarshal bidder config modifyingvastxmlallowed: %v", err) + } + infos[bidderName] = info + } + return infos, nil +} + // MarshalAccountDefaults compiles AccountDefaults into the JSON format used for merge patch func (cfg *Configuration) MarshalAccountDefaults() error { var err error diff --git a/config/config_test.go b/config/config_test.go index 535b4e7b099..b3c43dfa4d3 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,6 +5,7 @@ import ( "errors" "net" "os" + "strings" "testing" "time" @@ -1294,6 +1295,155 @@ func TestSpecialFeature1VendorExceptionMap(t *testing.T) { } } +func TestSetConfigBidderInfoNillableFields(t *testing.T) { + falseValue := false + trueValue := true + + bidder1ConfigFalses := []byte(` + adapters: + bidder1: + disabled: false + modifyingVastXmlAllowed: false`) + bidder1ConfigTrues := []byte(` + adapters: + bidder1: + disabled: true + modifyingVastXmlAllowed: true`) + bidder1ConfigNils := []byte(` + adapters: + bidder1: + disabled: null + modifyingVastXmlAllowed: null`) + bidder1Bidder2ConfigMixed := []byte(` + adapters: + bidder1: + disabled: true + modifyingVastXmlAllowed: false + bidder2: + disabled: false + modifyingVastXmlAllowed: true`) + + tests := []struct { + name string + rawConfig []byte + bidderInfos BidderInfos + expected nillableFieldBidderInfos + expectError bool + }{ + { + name: "viper and bidder infos are nil", + expected: nil, + }, + { + name: "viper is nil", + bidderInfos: map[string]BidderInfo{}, + expected: nil, + }, + { + name: "bidder infos is nil", + rawConfig: []byte{}, + expected: nil, + }, + { + name: "bidder infos is empty", + bidderInfos: map[string]BidderInfo{}, + expected: nil, + }, + { + name: "one: bidder info has nillable fields as false, viper has as nil", + bidderInfos: map[string]BidderInfo{ + "bidder1": {Disabled: false, ModifyingVastXmlAllowed: false}, + }, + rawConfig: bidder1ConfigNils, + expected: nillableFieldBidderInfos{ + "bidder1": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: nil, + ModifyingVastXmlAllowed: nil, + }, + bidderInfo: BidderInfo{Disabled: false, ModifyingVastXmlAllowed: false}, + }, + }, + }, + { + name: "one: bidder info has nillable fields as false, viper has as false", + bidderInfos: map[string]BidderInfo{ + "bidder1": {Disabled: false, ModifyingVastXmlAllowed: false}, + }, + rawConfig: bidder1ConfigFalses, + expected: nillableFieldBidderInfos{ + "bidder1": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: &falseValue, + ModifyingVastXmlAllowed: &falseValue, + }, + bidderInfo: BidderInfo{Disabled: false, ModifyingVastXmlAllowed: false}, + }, + }, + }, + { + name: "one: bidder info has nillable fields as false, viper has as true", + bidderInfos: map[string]BidderInfo{ + "bidder1": {Disabled: false, ModifyingVastXmlAllowed: false}, + }, + rawConfig: bidder1ConfigTrues, + expected: nillableFieldBidderInfos{ + "bidder1": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: &trueValue, + ModifyingVastXmlAllowed: &trueValue, + }, + bidderInfo: BidderInfo{Disabled: false, ModifyingVastXmlAllowed: false}, + }, + }, + }, + { + name: "many with extra info: bidder infos have nillable fields as false and true, viper has as true and false", + bidderInfos: map[string]BidderInfo{ + "bidder1": {Disabled: false, ModifyingVastXmlAllowed: true, Endpoint: "endpoint a"}, + "bidder2": {Disabled: true, ModifyingVastXmlAllowed: false, Endpoint: "endpoint b"}, + }, + rawConfig: bidder1Bidder2ConfigMixed, + expected: nillableFieldBidderInfos{ + "bidder1": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: &trueValue, + ModifyingVastXmlAllowed: &falseValue, + }, + bidderInfo: BidderInfo{Disabled: false, ModifyingVastXmlAllowed: true, Endpoint: "endpoint a"}, + }, + "bidder2": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: &falseValue, + ModifyingVastXmlAllowed: &trueValue, + }, + bidderInfo: BidderInfo{Disabled: true, ModifyingVastXmlAllowed: false, Endpoint: "endpoint b"}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := viper.New() + v.SetConfigType("yaml") + for bidderName := range tt.bidderInfos { + setBidderDefaults(v, strings.ToLower(bidderName)) + } + v.ReadConfig(bytes.NewBuffer(tt.rawConfig)) + + result, err := setConfigBidderInfoNillableFields(v, tt.bidderInfos) + + assert.Equal(t, tt.expected, result) + if tt.expectError { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + }) + } +} + func TestTCF2PurposeEnforced(t *testing.T) { tests := []struct { description string From 180eb410cb9f6e1751621797171cec7fa2096122 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:47:22 -0500 Subject: [PATCH 084/138] Fix: Revert JSON lib used to prepare /bidders/params response (#3300) --- router/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/router.go b/router/router.go index 0ee9fc7c30e..80d972a9f5b 100644 --- a/router/router.go +++ b/router/router.go @@ -92,7 +92,7 @@ func newJsonDirectoryServer(schemaDirectory string, validator openrtb_ext.Bidder data[aliasName] = bidderData } - response, err := jsonutil.Marshal(data) + response, err := json.Marshal(data) if err != nil { glog.Fatalf("Failed to marshal bidder param JSON-schema: %v", err) } From ea04a390902c9a793f3667827b0e1912d25af13e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:02:41 -0500 Subject: [PATCH 085/138] Bump golang.org/x/net from 0.7.0 to 0.17.0 (#3202) --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 912cdaa2aad..9f752b88257 100644 --- a/go.mod +++ b/go.mod @@ -31,8 +31,8 @@ require ( github.com/vrischmann/go-metrics-influxdb v0.1.1 github.com/xeipuuv/gojsonschema v1.2.0 github.com/yudai/gojsondiff v1.0.0 - golang.org/x/net v0.7.0 - golang.org/x/text v0.7.0 + golang.org/x/net v0.17.0 + golang.org/x/text v0.13.0 google.golang.org/grpc v1.53.0 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/yaml.v3 v3.0.1 @@ -69,8 +69,8 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect - golang.org/x/sys v0.5.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/sys v0.13.0 // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.66.4 // indirect diff --git a/go.sum b/go.sum index eefe0228261..76ced28500a 100644 --- a/go.sum +++ b/go.sum @@ -527,8 +527,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -613,8 +613,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -725,8 +725,8 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -739,8 +739,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From ee0869d21d31a7a2891e31e3c2b8bf75e08e600e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 11:34:24 -0500 Subject: [PATCH 086/138] Bump google.golang.org/grpc from 1.53.0 to 1.56.3 (#3258) --- go.mod | 12 ++++++------ go.sum | 19 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 9f752b88257..d152a3b6614 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,8 @@ require ( github.com/docker/go-units v0.4.0 github.com/go-sql-driver/mysql v1.6.0 github.com/gofrs/uuid v4.2.0+incompatible - github.com/golang/glog v1.0.0 + github.com/golang/glog v1.1.0 + github.com/json-iterator/go v1.1.12 github.com/julienschmidt/httprouter v1.3.0 github.com/lib/pq v1.10.4 github.com/mitchellh/copystructure v1.2.0 @@ -33,7 +34,7 @@ require ( github.com/yudai/gojsondiff v1.0.0 golang.org/x/net v0.17.0 golang.org/x/text v0.13.0 - google.golang.org/grpc v1.53.0 + google.golang.org/grpc v1.56.3 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -43,10 +44,9 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d // indirect - github.com/json-iterator/go v1.1.12 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -71,8 +71,8 @@ require ( github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/sys v0.13.0 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 76ced28500a..d05f428cef5 100644 --- a/go.sum +++ b/go.sum @@ -160,8 +160,8 @@ github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -191,8 +191,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -911,8 +912,8 @@ google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -943,8 +944,8 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -959,8 +960,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From fb94284028447674c65eef901cb7d42665d4e521 Mon Sep 17 00:00:00 2001 From: Nikhil Vaidya <102963966+pm-nikhil-vaidya@users.noreply.github.com> Date: Thu, 16 Nov 2023 22:27:01 +0530 Subject: [PATCH 087/138] Floors: Dynamic fetch (#2732) --- config/account.go | 57 +- config/account_test.go | 128 +- config/config.go | 36 +- config/config_test.go | 62 + endpoints/openrtb2/auction_benchmark_test.go | 1 + endpoints/openrtb2/test_utils.go | 1 + exchange/exchange.go | 16 +- exchange/exchange_test.go | 32 +- floors/fetcher.go | 331 +++++ floors/fetcher_test.go | 1279 ++++++++++++++++++ floors/floors.go | 105 +- floors/floors_test.go | 919 ++++++++++++- floors/rule.go | 7 +- go.mod | 1 + go.sum | 2 + openrtb_ext/floors.go | 62 + openrtb_ext/floors_test.go | 262 ++++ router/router.go | 15 +- 18 files changed, 3270 insertions(+), 46 deletions(-) create mode 100644 floors/fetcher.go create mode 100644 floors/fetcher_test.go diff --git a/config/account.go b/config/account.go index f1de54f79a2..ee131873e35 100644 --- a/config/account.go +++ b/config/account.go @@ -58,17 +58,30 @@ type AccountCCPA struct { } type AccountPriceFloors struct { - Enabled bool `mapstructure:"enabled" json:"enabled"` - EnforceFloorsRate int `mapstructure:"enforce_floors_rate" json:"enforce_floors_rate"` - AdjustForBidAdjustment bool `mapstructure:"adjust_for_bid_adjustment" json:"adjust_for_bid_adjustment"` - EnforceDealFloors bool `mapstructure:"enforce_deal_floors" json:"enforce_deal_floors"` - UseDynamicData bool `mapstructure:"use_dynamic_data" json:"use_dynamic_data"` - MaxRule int `mapstructure:"max_rules" json:"max_rules"` - MaxSchemaDims int `mapstructure:"max_schema_dims" json:"max_schema_dims"` + Enabled bool `mapstructure:"enabled" json:"enabled"` + EnforceFloorsRate int `mapstructure:"enforce_floors_rate" json:"enforce_floors_rate"` + AdjustForBidAdjustment bool `mapstructure:"adjust_for_bid_adjustment" json:"adjust_for_bid_adjustment"` + EnforceDealFloors bool `mapstructure:"enforce_deal_floors" json:"enforce_deal_floors"` + UseDynamicData bool `mapstructure:"use_dynamic_data" json:"use_dynamic_data"` + MaxRule int `mapstructure:"max_rules" json:"max_rules"` + MaxSchemaDims int `mapstructure:"max_schema_dims" json:"max_schema_dims"` + Fetcher AccountFloorFetch `mapstructure:"fetch" json:"fetch"` } -func (pf *AccountPriceFloors) validate(errs []error) []error { +// AccountFloorFetch defines the configuration for dynamic floors fetching. +type AccountFloorFetch struct { + Enabled bool `mapstructure:"enabled" json:"enabled"` + URL string `mapstructure:"url" json:"url"` + Timeout int `mapstructure:"timeout_ms" json:"timeout_ms"` + MaxFileSizeKB int `mapstructure:"max_file_size_kb" json:"max_file_size_kb"` + MaxRules int `mapstructure:"max_rules" json:"max_rules"` + MaxAge int `mapstructure:"max_age_sec" json:"max_age_sec"` + Period int `mapstructure:"period_sec" json:"period_sec"` + MaxSchemaDims int `mapstructure:"max_schema_dims" json:"max_schema_dims"` + AccountID string `mapstructure:"accountID" json:"accountID"` +} +func (pf *AccountPriceFloors) validate(errs []error) []error { if pf.EnforceFloorsRate < 0 || pf.EnforceFloorsRate > 100 { errs = append(errs, fmt.Errorf(`account_defaults.price_floors.enforce_floors_rate should be between 0 and 100`)) } @@ -81,6 +94,34 @@ func (pf *AccountPriceFloors) validate(errs []error) []error { errs = append(errs, fmt.Errorf(`account_defaults.price_floors.max_schema_dims should be between 0 and 20`)) } + if pf.Fetcher.Period > pf.Fetcher.MaxAge { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.period_sec should be less than account_defaults.price_floors.fetch.max_age_sec`)) + } + + if pf.Fetcher.Period < 300 { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.period_sec should not be less than 300 seconds`)) + } + + if pf.Fetcher.MaxAge < 600 { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.max_age_sec should not be less than 600 seconds and greater than maximum integer value`)) + } + + if !(pf.Fetcher.Timeout > 10 && pf.Fetcher.Timeout < 10000) { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.timeout_ms should be between 10 to 10,000 miliseconds`)) + } + + if pf.Fetcher.MaxRules < 0 { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.max_rules should be greater than or equal to 0`)) + } + + if pf.Fetcher.MaxFileSizeKB < 0 { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.max_file_size_kb should be greater than or equal to 0`)) + } + + if !(pf.Fetcher.MaxSchemaDims >= 0 && pf.Fetcher.MaxSchemaDims < 20) { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.max_schema_dims should not be less than 0 and greater than 20`)) + } + return errs } diff --git a/config/account_test.go b/config/account_test.go index 648618f2706..65e7f3e8716 100644 --- a/config/account_test.go +++ b/config/account_test.go @@ -795,7 +795,6 @@ func TestModulesGetConfig(t *testing.T) { } func TestAccountPriceFloorsValidate(t *testing.T) { - tests := []struct { description string pf *AccountPriceFloors @@ -807,12 +806,22 @@ func TestAccountPriceFloorsValidate(t *testing.T) { EnforceFloorsRate: 100, MaxRule: 200, MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, }, { description: "Invalid configuration: EnforceFloorRate:110", pf: &AccountPriceFloors{ EnforceFloorsRate: 110, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, want: []error{errors.New("account_defaults.price_floors.enforce_floors_rate should be between 0 and 100")}, }, @@ -820,6 +829,11 @@ func TestAccountPriceFloorsValidate(t *testing.T) { description: "Invalid configuration: EnforceFloorRate:-10", pf: &AccountPriceFloors{ EnforceFloorsRate: -10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, want: []error{errors.New("account_defaults.price_floors.enforce_floors_rate should be between 0 and 100")}, }, @@ -827,6 +841,11 @@ func TestAccountPriceFloorsValidate(t *testing.T) { description: "Invalid configuration: MaxRule:-20", pf: &AccountPriceFloors{ MaxRule: -20, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, want: []error{errors.New("account_defaults.price_floors.max_rules should be between 0 and 2147483647")}, }, @@ -834,9 +853,116 @@ func TestAccountPriceFloorsValidate(t *testing.T) { description: "Invalid configuration: MaxSchemaDims:100", pf: &AccountPriceFloors{ MaxSchemaDims: 100, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, want: []error{errors.New("account_defaults.price_floors.max_schema_dims should be between 0 and 20")}, }, + { + description: "Invalid period for fetch", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 100, + MaxAge: 600, + Timeout: 12, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.period_sec should not be less than 300 seconds")}, + }, + { + description: "Invalid max age for fetch", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 500, + Timeout: 12, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.max_age_sec should not be less than 600 seconds and greater than maximum integer value")}, + }, + { + description: "Period is greater than max age", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 700, + MaxAge: 600, + Timeout: 12, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.period_sec should be less than account_defaults.price_floors.fetch.max_age_sec")}, + }, + { + description: "Invalid timeout", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 4, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.timeout_ms should be between 10 to 10,000 miliseconds")}, + }, + { + description: "Invalid Max Rules", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + MaxRules: -2, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.max_rules should be greater than or equal to 0")}, + }, + { + description: "Invalid Max File size", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + MaxFileSizeKB: -1, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.max_file_size_kb should be greater than or equal to 0")}, + }, + { + description: "Invalid max_schema_dims", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + MaxFileSizeKB: 10, + MaxSchemaDims: 40, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.max_schema_dims should not be less than 0 and greater than 20")}, + }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { diff --git a/config/config.go b/config/config.go index 34c95822b3d..60a541e2ed0 100644 --- a/config/config.go +++ b/config/config.go @@ -103,7 +103,16 @@ type Configuration struct { } type PriceFloors struct { - Enabled bool `mapstructure:"enabled"` + Enabled bool `mapstructure:"enabled"` + Fetcher PriceFloorFetcher `mapstructure:"fetcher"` +} + +type PriceFloorFetcher struct { + HttpClient HTTPClient `mapstructure:"http_client"` + CacheSize int `mapstructure:"cache_size_mb"` + Worker int `mapstructure:"worker"` + Capacity int `mapstructure:"capacity"` + MaxRetries int `mapstructure:"max_retries"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -140,10 +149,6 @@ func (cfg *Configuration) validate(v *viper.Viper) []error { glog.Warning(`account_defaults.events has no effect as the feature is under development.`) } - if cfg.PriceFloors.Enabled { - glog.Warning(`cfg.PriceFloors.Enabled will currently not do anything as price floors feature is still under development.`) - } - errs = cfg.Experiment.validate(errs) errs = cfg.BidderInfos.validate(errs) errs = cfg.AccountDefaults.Privacy.IPv6Config.Validate(errs) @@ -1094,9 +1099,30 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("account_defaults.price_floors.use_dynamic_data", false) v.SetDefault("account_defaults.price_floors.max_rules", 100) v.SetDefault("account_defaults.price_floors.max_schema_dims", 3) + v.SetDefault("account_defaults.price_floors.fetch.enabled", false) + v.SetDefault("account_defaults.price_floors.fetch.url", "") + v.SetDefault("account_defaults.price_floors.fetch.timeout_ms", 3000) + v.SetDefault("account_defaults.price_floors.fetch.max_file_size_kb", 100) + v.SetDefault("account_defaults.price_floors.fetch.max_rules", 1000) + v.SetDefault("account_defaults.price_floors.fetch.max_age_sec", 86400) + v.SetDefault("account_defaults.price_floors.fetch.period_sec", 3600) + v.SetDefault("account_defaults.price_floors.fetch.max_schema_dims", 0) + + v.SetDefault("account_defaults.events_enabled", false) v.SetDefault("account_defaults.privacy.ipv6.anon_keep_bits", 56) v.SetDefault("account_defaults.privacy.ipv4.anon_keep_bits", 24) + //Defaults for Price floor fetcher + v.SetDefault("price_floors.fetcher.worker", 20) + v.SetDefault("price_floors.fetcher.capacity", 20000) + v.SetDefault("price_floors.fetcher.cache_size_mb", 64) + v.SetDefault("price_floors.fetcher.http_client.max_connections_per_host", 0) // unlimited + v.SetDefault("price_floors.fetcher.http_client.max_idle_connections", 40) + v.SetDefault("price_floors.fetcher.http_client.max_idle_connections_per_host", 2) + v.SetDefault("price_floors.fetcher.http_client.idle_connection_timeout_seconds", 60) + v.SetDefault("price_floors.fetcher.max_retries", 10) + + v.SetDefault("account_defaults.events_enabled", false) v.SetDefault("compression.response.enable_gzip", false) v.SetDefault("compression.request.enable_gzip", false) diff --git a/config/config_test.go b/config/config_test.go index b3c43dfa4d3..a551c1be66e 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -175,6 +175,14 @@ func TestDefaults(t *testing.T) { //Assert the price floor default values cmpBools(t, "price_floors.enabled", false, cfg.PriceFloors.Enabled) + cmpInts(t, "price_floors.fetcher.worker", 20, cfg.PriceFloors.Fetcher.Worker) + cmpInts(t, "price_floors.fetcher.capacity", 20000, cfg.PriceFloors.Fetcher.Capacity) + cmpInts(t, "price_floors.fetcher.cache_size_mb", 64, cfg.PriceFloors.Fetcher.CacheSize) + cmpInts(t, "price_floors.fetcher.http_client.max_connections_per_host", 0, cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost) + cmpInts(t, "price_floors.fetcher.http_client.max_idle_connections", 40, cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns) + cmpInts(t, "price_floors.fetcher.http_client.max_idle_connections_per_host", 2, cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConnsPerHost) + cmpInts(t, "price_floors.fetcher.http_client.idle_connection_timeout_seconds", 60, cfg.PriceFloors.Fetcher.HttpClient.IdleConnTimeout) + cmpInts(t, "price_floors.fetcher.max_retries", 10, cfg.PriceFloors.Fetcher.MaxRetries) // Assert compression related defaults cmpBools(t, "compression.request.enable_gzip", false, cfg.Compression.Request.GZIP) @@ -187,6 +195,15 @@ func TestDefaults(t *testing.T) { cmpBools(t, "account_defaults.price_floors.use_dynamic_data", false, cfg.AccountDefaults.PriceFloors.UseDynamicData) cmpInts(t, "account_defaults.price_floors.max_rules", 100, cfg.AccountDefaults.PriceFloors.MaxRule) cmpInts(t, "account_defaults.price_floors.max_schema_dims", 3, cfg.AccountDefaults.PriceFloors.MaxSchemaDims) + cmpBools(t, "account_defaults.price_floors.fetch.enabled", false, cfg.AccountDefaults.PriceFloors.Fetcher.Enabled) + cmpStrings(t, "account_defaults.price_floors.fetch.url", "", cfg.AccountDefaults.PriceFloors.Fetcher.URL) + cmpInts(t, "account_defaults.price_floors.fetch.timeout_ms", 3000, cfg.AccountDefaults.PriceFloors.Fetcher.Timeout) + cmpInts(t, "account_defaults.price_floors.fetch.max_file_size_kb", 100, cfg.AccountDefaults.PriceFloors.Fetcher.MaxFileSizeKB) + cmpInts(t, "account_defaults.price_floors.fetch.max_rules", 1000, cfg.AccountDefaults.PriceFloors.Fetcher.MaxRules) + cmpInts(t, "account_defaults.price_floors.fetch.period_sec", 3600, cfg.AccountDefaults.PriceFloors.Fetcher.Period) + cmpInts(t, "account_defaults.price_floors.fetch.max_age_sec", 86400, cfg.AccountDefaults.PriceFloors.Fetcher.MaxAge) + cmpInts(t, "account_defaults.price_floors.fetch.max_schema_dims", 0, cfg.AccountDefaults.PriceFloors.Fetcher.MaxSchemaDims) + cmpBools(t, "account_defaults.events.enabled", false, cfg.AccountDefaults.Events.Enabled) cmpBools(t, "hooks.enabled", false, cfg.Hooks.Enabled) @@ -451,6 +468,16 @@ hooks: enabled: true price_floors: enabled: true + fetcher: + worker: 20 + capacity: 20000 + cache_size_mb: 8 + http_client: + max_connections_per_host: 5 + max_idle_connections: 1 + max_idle_connections_per_host: 2 + idle_connection_timeout_seconds: 10 + max_retries: 5 account_defaults: events: enabled: true @@ -462,6 +489,15 @@ account_defaults: use_dynamic_data: true max_rules: 120 max_schema_dims: 5 + fetch: + enabled: true + url: http://test.com/floors + timeout_ms: 500 + max_file_size_kb: 200 + max_rules: 500 + period_sec: 2000 + max_age_sec: 6000 + max_schema_dims: 10 privacy: ipv6: anon_keep_bits: 50 @@ -557,6 +593,14 @@ func TestFullConfig(t *testing.T) { //Assert the price floor values cmpBools(t, "price_floors.enabled", true, cfg.PriceFloors.Enabled) + cmpInts(t, "price_floors.fetcher.worker", 20, cfg.PriceFloors.Fetcher.Worker) + cmpInts(t, "price_floors.fetcher.capacity", 20000, cfg.PriceFloors.Fetcher.Capacity) + cmpInts(t, "price_floors.fetcher.cache_size_mb", 8, cfg.PriceFloors.Fetcher.CacheSize) + cmpInts(t, "price_floors.fetcher.http_client.max_connections_per_host", 5, cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost) + cmpInts(t, "price_floors.fetcher.http_client.max_idle_connections", 1, cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns) + cmpInts(t, "price_floors.fetcher.http_client.max_idle_connections_per_host", 2, cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConnsPerHost) + cmpInts(t, "price_floors.fetcher.http_client.idle_connection_timeout_seconds", 10, cfg.PriceFloors.Fetcher.HttpClient.IdleConnTimeout) + cmpInts(t, "price_floors.fetcher.max_retries", 5, cfg.PriceFloors.Fetcher.MaxRetries) cmpBools(t, "account_defaults.price_floors.enabled", true, cfg.AccountDefaults.PriceFloors.Enabled) cmpInts(t, "account_defaults.price_floors.enforce_floors_rate", 50, cfg.AccountDefaults.PriceFloors.EnforceFloorsRate) cmpBools(t, "account_defaults.price_floors.adjust_for_bid_adjustment", false, cfg.AccountDefaults.PriceFloors.AdjustForBidAdjustment) @@ -564,6 +608,15 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "account_defaults.price_floors.use_dynamic_data", true, cfg.AccountDefaults.PriceFloors.UseDynamicData) cmpInts(t, "account_defaults.price_floors.max_rules", 120, cfg.AccountDefaults.PriceFloors.MaxRule) cmpInts(t, "account_defaults.price_floors.max_schema_dims", 5, cfg.AccountDefaults.PriceFloors.MaxSchemaDims) + cmpBools(t, "account_defaults.price_floors.fetch.enabled", true, cfg.AccountDefaults.PriceFloors.Fetcher.Enabled) + cmpStrings(t, "account_defaults.price_floors.fetch.url", "http://test.com/floors", cfg.AccountDefaults.PriceFloors.Fetcher.URL) + cmpInts(t, "account_defaults.price_floors.fetch.timeout_ms", 500, cfg.AccountDefaults.PriceFloors.Fetcher.Timeout) + cmpInts(t, "account_defaults.price_floors.fetch.max_file_size_kb", 200, cfg.AccountDefaults.PriceFloors.Fetcher.MaxFileSizeKB) + cmpInts(t, "account_defaults.price_floors.fetch.max_rules", 500, cfg.AccountDefaults.PriceFloors.Fetcher.MaxRules) + cmpInts(t, "account_defaults.price_floors.fetch.period_sec", 2000, cfg.AccountDefaults.PriceFloors.Fetcher.Period) + cmpInts(t, "account_defaults.price_floors.fetch.max_age_sec", 6000, cfg.AccountDefaults.PriceFloors.Fetcher.MaxAge) + cmpInts(t, "account_defaults.price_floors.fetch.max_schema_dims", 10, cfg.AccountDefaults.PriceFloors.Fetcher.MaxSchemaDims) + cmpBools(t, "account_defaults.events.enabled", true, cfg.AccountDefaults.Events.Enabled) cmpInts(t, "account_defaults.privacy.ipv6.anon_keep_bits", 50, cfg.AccountDefaults.Privacy.IPv6Config.AnonKeepBits) @@ -772,6 +825,15 @@ func TestValidateConfig(t *testing.T) { Files: FileFetcherConfig{Enabled: true}, InMemoryCache: InMemoryCache{Type: "none"}, }, + AccountDefaults: Account{ + PriceFloors: AccountPriceFloors{ + Fetcher: AccountFloorFetch{ + Timeout: 100, + Period: 300, + MaxAge: 600, + }, + }, + }, } v := viper.New() diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index 88a55e627e7..835d2920af4 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -95,6 +95,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { empty_fetcher.EmptyFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), + nil, ) endpoint, _ := NewEndpoint( diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index cfc8f5d8dac..e3e7803e551 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -1204,6 +1204,7 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid mockFetcher, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), + nil, ) testExchange = &exchangeTestWrapper{ diff --git a/exchange/exchange.go b/exchange/exchange.go index b07c4bdf2a5..bb59fe90d43 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -81,7 +81,8 @@ type exchange struct { bidValidationEnforcement config.Validations requestSplitter requestSplitter macroReplacer macros.Replacer - floor config.PriceFloors + priceFloorEnabled bool + priceFloorFetcher floors.FloorFetcher } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -130,7 +131,7 @@ func (randomDeduplicateBidBooleanGenerator) Generate() bool { return rand.Intn(100) < 50 } -func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gdprPermsBuilder gdpr.PermissionsBuilder, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher, adsCertSigner adscert.Signer, macroReplacer macros.Replacer) Exchange { +func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gdprPermsBuilder gdpr.PermissionsBuilder, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher, adsCertSigner adscert.Signer, macroReplacer macros.Replacer, priceFloorFetcher floors.FloorFetcher) Exchange { bidderToSyncerKey := map[string]string{} for bidder, syncer := range syncersByBidder { bidderToSyncerKey[bidder] = syncer.Key() @@ -175,7 +176,8 @@ func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid bidValidationEnforcement: cfg.Validations, requestSplitter: requestSplitter, macroReplacer: macroReplacer, - floor: cfg.PriceFloors, + priceFloorEnabled: cfg.PriceFloors.Enabled, + priceFloorFetcher: priceFloorFetcher, } } @@ -233,7 +235,6 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog return nil, nil } - var floorErrs []error err := r.HookExecutor.ExecuteProcessedAuctionStage(r.BidRequestWrapper) if err != nil { return nil, err @@ -268,8 +269,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog // Get currency rates conversions for the auction conversions := e.getAuctionCurrencyRates(requestExtPrebid.CurrencyConversions) - if e.floor.Enabled { - floorErrs = floors.EnrichWithPriceFloors(r.BidRequestWrapper, r.Account, conversions) + var floorErrs []error + if e.priceFloorEnabled { + floorErrs = floors.EnrichWithPriceFloors(r.BidRequestWrapper, r.Account, conversions, e.priceFloorFetcher) } responseDebugAllow, accountDebugAllow, debugLog := getDebugInfo(r.BidRequestWrapper.Test, requestExtPrebid, r.Account.DebugAllow, debugLog) @@ -386,7 +388,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog ) if anyBidsReturned { - if e.floor.Enabled { + if e.priceFloorEnabled { var rejectedBids []*entities.PbsOrtbSeatBid var enforceErrs []error diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index bfeb9374af3..a537054cbce 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -83,7 +83,7 @@ func TestNewExchange(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) for _, bidderName := range knownAdapters { if _, ok := e.adapterMap[bidderName]; !ok { if biddersInfo[string(bidderName)].IsEnabled() { @@ -133,7 +133,7 @@ func TestCharacterEscape(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs //liveAdapters []openrtb_ext.BidderName, @@ -816,6 +816,14 @@ func TestAdapterCurrency(t *testing.T) { } } +type mockPriceFloorFetcher struct{} + +func (mpf *mockPriceFloorFetcher) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + return nil, openrtb_ext.FetchNone +} + +func (mpf *mockPriceFloorFetcher) Stop() {} + func TestFloorsSignalling(t *testing.T) { fakeCurrencyClient := &fakeCurrencyRatesHttpClient{ @@ -840,7 +848,8 @@ func TestFloorsSignalling(t *testing.T) { currencyConverter: currencyConverter, categoriesFetcher: nilCategoryFetcher{}, bidIDGenerator: &mockBidIDGenerator{false, false}, - floor: config.PriceFloors{Enabled: true}, + priceFloorEnabled: true, + priceFloorFetcher: &mockPriceFloorFetcher{}, } e.requestSplitter = requestSplitter{ me: e.me, @@ -1428,7 +1437,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { }, }.Builder - e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs liveAdapters := []openrtb_ext.BidderName{bidderName} @@ -1785,7 +1794,7 @@ func TestBidResponseCurrency(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -1931,7 +1940,7 @@ func TestBidResponseImpExtInfo(t *testing.T) { t.Fatalf("Error intializing adapters: %v", adaptersErr) } - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, nil, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, nil, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -2023,7 +2032,7 @@ func TestRaceIntegration(t *testing.T) { }, }.Builder - ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) _, err = ex.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) @@ -2121,7 +2130,7 @@ func TestPanicRecovery(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) chBids := make(chan *bidResponseWrapper, 1) panicker := func(bidderRequest BidderRequest, conversions currency.Conversions) { @@ -2191,7 +2200,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { allowAllBidders: true, }, }.Builder - e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, categoriesFetcher, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, categoriesFetcher, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{} e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{} @@ -2634,7 +2643,8 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] server: config.Server{ExternalUrl: server.ExternalUrl, GvlID: server.GvlID, DataCenter: server.DataCenter}, bidValidationEnforcement: hostBidValidation, requestSplitter: requestSplitter, - floor: config.PriceFloors{Enabled: floorsFlag}, + priceFloorEnabled: floorsFlag, + priceFloorFetcher: &mockPriceFloorFetcher{}, } } @@ -4660,7 +4670,7 @@ func TestPassExperimentConfigsToHoldAuction(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &signer, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &signer, macros.NewStringIndexBasedReplacer(), nil).(*exchange) // Define mock incoming bid requeset mockBidRequest := &openrtb2.BidRequest{ diff --git a/floors/fetcher.go b/floors/fetcher.go new file mode 100644 index 00000000000..6df43445010 --- /dev/null +++ b/floors/fetcher.go @@ -0,0 +1,331 @@ +package floors + +import ( + "container/heap" + "context" + "encoding/json" + "errors" + "io" + "math" + "net/http" + "strconv" + "time" + + "github.com/alitto/pond" + validator "github.com/asaskevich/govalidator" + "github.com/coocood/freecache" + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/timeutil" +) + +var refetchCheckInterval = 300 + +type fetchInfo struct { + config.AccountFloorFetch + fetchTime int64 + refetchRequest bool + retryCount int +} + +type WorkerPool interface { + TrySubmit(task func()) bool + Stop() +} + +type FloorFetcher interface { + Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) + Stop() +} + +type PriceFloorFetcher struct { + pool WorkerPool // Goroutines worker pool + fetchQueue FetchQueue // Priority Queue to fetch floor data + fetchInProgress map[string]bool // Map of URL with fetch status + configReceiver chan fetchInfo // Channel which recieves URLs to be fetched + done chan struct{} // Channel to close fetcher + cache *freecache.Cache // cache + httpClient *http.Client // http client to fetch data from url + time timeutil.Time // time interface to record request timings + metricEngine metrics.MetricsEngine // Records malfunctions in dynamic fetch + maxRetries int // Max number of retries for failing URLs +} + +type FetchQueue []*fetchInfo + +func (fq FetchQueue) Len() int { + return len(fq) +} + +func (fq FetchQueue) Less(i, j int) bool { + return fq[i].fetchTime < fq[j].fetchTime +} + +func (fq FetchQueue) Swap(i, j int) { + fq[i], fq[j] = fq[j], fq[i] +} + +func (fq *FetchQueue) Push(element interface{}) { + fetchInfo := element.(*fetchInfo) + *fq = append(*fq, fetchInfo) +} + +func (fq *FetchQueue) Pop() interface{} { + old := *fq + n := len(old) + fetchInfo := old[n-1] + old[n-1] = nil + *fq = old[0 : n-1] + return fetchInfo +} + +func (fq *FetchQueue) Top() *fetchInfo { + old := *fq + if len(old) == 0 { + return nil + } + return old[0] +} + +func workerPanicHandler(p interface{}) { + glog.Errorf("floor fetcher worker panicked: %v", p) +} + +func NewPriceFloorFetcher(config config.PriceFloors, httpClient *http.Client, metricEngine metrics.MetricsEngine) *PriceFloorFetcher { + if !config.Enabled { + return nil + } + + floorFetcher := PriceFloorFetcher{ + pool: pond.New(config.Fetcher.Worker, config.Fetcher.Capacity, pond.PanicHandler(workerPanicHandler)), + fetchQueue: make(FetchQueue, 0, 100), + fetchInProgress: make(map[string]bool), + configReceiver: make(chan fetchInfo, config.Fetcher.Capacity), + done: make(chan struct{}), + cache: freecache.NewCache(config.Fetcher.CacheSize * 1024 * 1024), + httpClient: httpClient, + time: &timeutil.RealTime{}, + metricEngine: metricEngine, + maxRetries: config.Fetcher.MaxRetries, + } + + go floorFetcher.Fetcher() + + return &floorFetcher +} + +func (f *PriceFloorFetcher) SetWithExpiry(key string, value json.RawMessage, cacheExpiry int) { + f.cache.Set([]byte(key), value, cacheExpiry) +} + +func (f *PriceFloorFetcher) Get(key string) (json.RawMessage, bool) { + data, err := f.cache.Get([]byte(key)) + if err != nil { + return nil, false + } + + return data, true +} + +func (f *PriceFloorFetcher) Fetch(config config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + if f == nil || !config.UseDynamicData || len(config.Fetcher.URL) == 0 || !validator.IsURL(config.Fetcher.URL) { + return nil, openrtb_ext.FetchNone + } + + // Check for floors JSON in cache + if result, found := f.Get(config.Fetcher.URL); found { + var fetchedFloorData openrtb_ext.PriceFloorRules + if err := json.Unmarshal(result, &fetchedFloorData); err != nil || fetchedFloorData.Data == nil { + return nil, openrtb_ext.FetchError + } + return &fetchedFloorData, openrtb_ext.FetchSuccess + } + + //miss: push to channel to fetch and return empty response + if config.Enabled && config.Fetcher.Enabled && config.Fetcher.Timeout > 0 { + fetchConfig := fetchInfo{AccountFloorFetch: config.Fetcher, fetchTime: f.time.Now().Unix(), refetchRequest: false, retryCount: 0} + f.configReceiver <- fetchConfig + } + + return nil, openrtb_ext.FetchInprogress +} + +func (f *PriceFloorFetcher) worker(fetchConfig fetchInfo) { + floorData, fetchedMaxAge := f.fetchAndValidate(fetchConfig.AccountFloorFetch) + if floorData != nil { + // Reset retry count when data is successfully fetched + fetchConfig.retryCount = 0 + + // Update cache with new floor rules + cacheExpiry := fetchConfig.AccountFloorFetch.MaxAge + if fetchedMaxAge != 0 { + cacheExpiry = fetchedMaxAge + } + floorData, err := json.Marshal(floorData) + if err != nil { + glog.Errorf("Error while marshaling fetched floor data for url %s", fetchConfig.AccountFloorFetch.URL) + } else { + f.SetWithExpiry(fetchConfig.AccountFloorFetch.URL, floorData, cacheExpiry) + } + } else { + fetchConfig.retryCount++ + } + + // Send to refetch channel + if fetchConfig.retryCount < f.maxRetries { + fetchConfig.fetchTime = f.time.Now().Add(time.Duration(fetchConfig.AccountFloorFetch.Period) * time.Second).Unix() + fetchConfig.refetchRequest = true + f.configReceiver <- fetchConfig + } +} + +// Stop terminates price floor fetcher +func (f *PriceFloorFetcher) Stop() { + if f == nil { + return + } + + close(f.done) + f.pool.Stop() + close(f.configReceiver) +} + +func (f *PriceFloorFetcher) submit(fetchConfig *fetchInfo) { + status := f.pool.TrySubmit(func() { + f.worker(*fetchConfig) + }) + if !status { + heap.Push(&f.fetchQueue, fetchConfig) + } +} + +func (f *PriceFloorFetcher) Fetcher() { + //Create Ticker of 5 minutes + ticker := time.NewTicker(time.Duration(refetchCheckInterval) * time.Second) + + for { + select { + case fetchConfig := <-f.configReceiver: + if fetchConfig.refetchRequest { + heap.Push(&f.fetchQueue, &fetchConfig) + } else { + if _, ok := f.fetchInProgress[fetchConfig.URL]; !ok { + f.fetchInProgress[fetchConfig.URL] = true + f.submit(&fetchConfig) + } + } + case <-ticker.C: + currentTime := f.time.Now().Unix() + for top := f.fetchQueue.Top(); top != nil && top.fetchTime <= currentTime; top = f.fetchQueue.Top() { + nextFetch := heap.Pop(&f.fetchQueue) + f.submit(nextFetch.(*fetchInfo)) + } + case <-f.done: + ticker.Stop() + glog.Info("Price Floor fetcher terminated") + return + } + } +} + +func (f *PriceFloorFetcher) fetchAndValidate(config config.AccountFloorFetch) (*openrtb_ext.PriceFloorRules, int) { + floorResp, maxAge, err := f.fetchFloorRulesFromURL(config) + if floorResp == nil || err != nil { + glog.Errorf("Error while fetching floor data from URL: %s, reason : %s", config.URL, err.Error()) + return nil, 0 + } + + if len(floorResp) > (config.MaxFileSizeKB * 1024) { + glog.Errorf("Recieved invalid floor data from URL: %s, reason : floor file size is greater than MaxFileSize", config.URL) + return nil, 0 + } + + var priceFloors openrtb_ext.PriceFloorRules + if err = json.Unmarshal(floorResp, &priceFloors.Data); err != nil { + glog.Errorf("Recieved invalid price floor json from URL: %s", config.URL) + return nil, 0 + } + + if err := validateRules(config, &priceFloors); err != nil { + glog.Errorf("Validation failed for floor JSON from URL: %s, reason: %s", config.URL, err.Error()) + return nil, 0 + } + + return &priceFloors, maxAge +} + +// fetchFloorRulesFromURL returns a price floor JSON and time for which this JSON is valid +// from provided URL with timeout constraints +func (f *PriceFloorFetcher) fetchFloorRulesFromURL(config config.AccountFloorFetch) ([]byte, int, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(config.Timeout)*time.Millisecond) + defer cancel() + + httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, config.URL, nil) + if err != nil { + return nil, 0, errors.New("error while forming http fetch request : " + err.Error()) + } + + httpResp, err := f.httpClient.Do(httpReq) + if err != nil { + return nil, 0, errors.New("error while getting response from url : " + err.Error()) + } + + if httpResp.StatusCode != http.StatusOK { + return nil, 0, errors.New("no response from server") + } + + var maxAge int + if maxAgeStr := httpResp.Header.Get("max-age"); maxAgeStr != "" { + maxAge, err = strconv.Atoi(maxAgeStr) + if err != nil { + glog.Errorf("max-age in header is malformed for url %s", config.URL) + } + if maxAge <= config.Period || maxAge > math.MaxInt32 { + glog.Errorf("Invalid max-age = %s provided, value should be valid integer and should be within (%v, %v)", maxAgeStr, config.Period, math.MaxInt32) + } + } + + respBody, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, 0, errors.New("unable to read response") + } + defer httpResp.Body.Close() + + return respBody, maxAge, nil +} + +func validateRules(config config.AccountFloorFetch, priceFloors *openrtb_ext.PriceFloorRules) error { + if priceFloors.Data == nil { + return errors.New("empty data in floor JSON") + } + + if len(priceFloors.Data.ModelGroups) == 0 { + return errors.New("no model groups found in price floor data") + } + + if priceFloors.Data.SkipRate < 0 || priceFloors.Data.SkipRate > 100 { + return errors.New("skip rate should be greater than or equal to 0 and less than 100") + } + + for _, modelGroup := range priceFloors.Data.ModelGroups { + if len(modelGroup.Values) == 0 || len(modelGroup.Values) > config.MaxRules { + return errors.New("invalid number of floor rules, floor rules should be greater than zero and less than MaxRules specified in account config") + } + + if modelGroup.ModelWeight != nil && (*modelGroup.ModelWeight < 1 || *modelGroup.ModelWeight > 100) { + return errors.New("modelGroup[].modelWeight should be greater than or equal to 1 and less than 100") + } + + if modelGroup.SkipRate < 0 || modelGroup.SkipRate > 100 { + return errors.New("model group skip rate should be greater than or equal to 0 and less than 100") + } + + if modelGroup.Default < 0 { + return errors.New("modelGroup.Default should be greater than 0") + } + } + + return nil +} diff --git a/floors/fetcher_test.go b/floors/fetcher_test.go new file mode 100644 index 00000000000..085fd3edd1b --- /dev/null +++ b/floors/fetcher_test.go @@ -0,0 +1,1279 @@ +package floors + +import ( + "encoding/json" + "fmt" + "math/rand" + "net/http" + "net/http/httptest" + "reflect" + "testing" + "time" + + "github.com/alitto/pond" + "github.com/coocood/freecache" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/timeutil" + "github.com/stretchr/testify/assert" +) + +const MaxAge = "max-age" + +func TestFetchQueueLen(t *testing.T) { + tests := []struct { + name string + fq FetchQueue + want int + }{ + { + name: "Queue is empty", + fq: make(FetchQueue, 0), + want: 0, + }, + { + name: "Queue is of lenght 1", + fq: make(FetchQueue, 1), + want: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fq.Len(); got != tt.want { + t.Errorf("FetchQueue.Len() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFetchQueueLess(t *testing.T) { + type args struct { + i int + j int + } + tests := []struct { + name string + fq FetchQueue + args args + want bool + }{ + { + name: "first fetchperiod is less than second", + fq: FetchQueue{&fetchInfo{fetchTime: 10}, &fetchInfo{fetchTime: 20}}, + args: args{i: 0, j: 1}, + want: true, + }, + { + name: "first fetchperiod is greater than second", + fq: FetchQueue{&fetchInfo{fetchTime: 30}, &fetchInfo{fetchTime: 10}}, + args: args{i: 0, j: 1}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fq.Less(tt.args.i, tt.args.j); got != tt.want { + t.Errorf("FetchQueue.Less() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFetchQueueSwap(t *testing.T) { + type args struct { + i int + j int + } + tests := []struct { + name string + fq FetchQueue + args args + }{ + { + name: "Swap two elements at index i and j", + fq: FetchQueue{&fetchInfo{fetchTime: 30}, &fetchInfo{fetchTime: 10}}, + args: args{i: 0, j: 1}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fInfo1, fInfo2 := tt.fq[0], tt.fq[1] + tt.fq.Swap(tt.args.i, tt.args.j) + assert.Equal(t, fInfo1, tt.fq[1], "elements are not swapped") + assert.Equal(t, fInfo2, tt.fq[0], "elements are not swapped") + }) + } +} + +func TestFetchQueuePush(t *testing.T) { + type args struct { + element interface{} + } + tests := []struct { + name string + fq *FetchQueue + args args + }{ + { + name: "Push element to queue", + fq: &FetchQueue{}, + args: args{element: &fetchInfo{fetchTime: 10}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.fq.Push(tt.args.element) + q := *tt.fq + assert.Equal(t, q[0], &fetchInfo{fetchTime: 10}) + }) + } +} + +func TestFetchQueuePop(t *testing.T) { + tests := []struct { + name string + fq *FetchQueue + want interface{} + }{ + { + name: "Pop element from queue", + fq: &FetchQueue{&fetchInfo{fetchTime: 10}}, + want: &fetchInfo{fetchTime: 10}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fq.Pop(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("FetchQueue.Pop() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFetchQueueTop(t *testing.T) { + tests := []struct { + name string + fq *FetchQueue + want *fetchInfo + }{ + { + name: "Get top element from queue", + fq: &FetchQueue{&fetchInfo{fetchTime: 20}}, + want: &fetchInfo{fetchTime: 20}, + }, + { + name: "Queue is empty", + fq: &FetchQueue{}, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fq.Top(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("FetchQueue.Top() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestValidatePriceFloorRules(t *testing.T) { + var zero = 0 + var one_o_one = 101 + var testURL = "abc.com" + type args struct { + configs config.AccountFloorFetch + priceFloors *openrtb_ext.PriceFloorRules + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Price floor data is empty", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 5, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{}, + }, + wantErr: true, + }, + { + name: "Model group array is empty", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 5, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{}, + }, + }, + wantErr: true, + }, + { + name: "floor rules is empty", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 5, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{}, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "floor rules is grater than max floor rules", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 0, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Modelweight is zero", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + ModelWeight: &zero, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Modelweight is 101", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + ModelWeight: &one_o_one, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "skiprate is 101", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + SkipRate: 101, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Default is -1", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + Default: -1, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Invalid skip rate in data", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + SkipRate: -44, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + }}, + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateRules(tt.args.configs, tt.args.priceFloors); (err != nil) != tt.wantErr { + t.Errorf("validatePriceFloorRules() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestFetchFloorRulesFromURL(t *testing.T) { + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("Content-Length", "645") + w.Header().Add(MaxAge, "20") + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + type args struct { + configs config.AccountFloorFetch + } + tests := []struct { + name string + args args + response []byte + responseStatus int + want []byte + want1 int + wantErr bool + }{ + { + name: "Floor data is successfully returned", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 60, + Period: 300, + }, + }, + response: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + responseStatus: 200, + want: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + want1: 20, + wantErr: false, + }, + { + name: "Time out occured", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 0, + Period: 300, + }, + }, + want1: 0, + responseStatus: 200, + wantErr: true, + }, + { + name: "Invalid URL", + args: args{ + configs: config.AccountFloorFetch{ + URL: "%%", + Timeout: 10, + Period: 300, + }, + }, + want1: 0, + responseStatus: 200, + wantErr: true, + }, + { + name: "No response from server", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 10, + Period: 300, + }, + }, + want1: 0, + responseStatus: 500, + wantErr: true, + }, + { + name: "Invalid response", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 10, + Period: 300, + }, + }, + want1: 0, + response: []byte("1"), + responseStatus: 200, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) + defer mockHttpServer.Close() + + if tt.args.configs.URL == "" { + tt.args.configs.URL = mockHttpServer.URL + } + pff := PriceFloorFetcher{ + httpClient: mockHttpServer.Client(), + } + got, got1, err := pff.fetchFloorRulesFromURL(tt.args.configs) + if (err != nil) != tt.wantErr { + t.Errorf("fetchFloorRulesFromURL() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("fetchFloorRulesFromURL() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("fetchFloorRulesFromURL() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestFetchFloorRulesFromURLInvalidMaxAge(t *testing.T) { + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("Content-Length", "645") + w.Header().Add(MaxAge, "abc") + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + type args struct { + configs config.AccountFloorFetch + } + tests := []struct { + name string + args args + response []byte + responseStatus int + want []byte + want1 int + wantErr bool + }{ + { + name: "Floor data is successfully returned", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 60, + Period: 300, + }, + }, + response: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + responseStatus: 200, + want: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + want1: 0, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) + defer mockHttpServer.Close() + + if tt.args.configs.URL == "" { + tt.args.configs.URL = mockHttpServer.URL + } + + ppf := PriceFloorFetcher{ + httpClient: mockHttpServer.Client(), + } + got, got1, err := ppf.fetchFloorRulesFromURL(tt.args.configs) + if (err != nil) != tt.wantErr { + t.Errorf("fetchFloorRulesFromURL() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("fetchFloorRulesFromURL() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("fetchFloorRulesFromURL() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestFetchAndValidate(t *testing.T) { + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add(MaxAge, "30") + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + type args struct { + configs config.AccountFloorFetch + } + tests := []struct { + name string + args args + response []byte + responseStatus int + want *openrtb_ext.PriceFloorRules + want1 int + }{ + { + name: "Recieved valid price floor rules response", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 700, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: func() []byte { + data := `{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}` + return []byte(data) + }(), + responseStatus: 200, + want: func() *openrtb_ext.PriceFloorRules { + var res openrtb_ext.PriceFloorRules + data := `{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}` + _ = json.Unmarshal([]byte(data), &res.Data) + return &res + }(), + want1: 30, + }, + { + name: "No response from server", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 700, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: []byte{}, + responseStatus: 500, + want: nil, + want1: 0, + }, + { + name: "File is greater than MaxFileSize", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 1, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: func() []byte { + data := `{"currency":"USD","floorProvider":"PM","floorsSchemaVersion":2,"modelGroups":[{"modelVersion":"M_0","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.85,"www.missyusa.com":0.7}},{"modelVersion":"M_1","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":1,"www.missyusa.com":1.85}},{"modelVersion":"M_2","modelWeight":5,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.6,"www.missyusa.com":0.7}},{"modelVersion":"M_3","modelWeight":2,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.9,"www.missyusa.com":0.75}},{"modelVersion":"M_4","modelWeight":1,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":1.35,"missyusa.com":1.75}},{"modelVersion":"M_5","modelWeight":2,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.4,"www.missyusa.com":0.9}},{"modelVersion":"M_6","modelWeight":43,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":2,"missyusa.com":2}},{"modelVersion":"M_7","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.4,"www.missyusa.com":1.85}},{"modelVersion":"M_8","modelWeight":3,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":1.7,"missyusa.com":0.1}},{"modelVersion":"M_9","modelWeight":7,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.9,"www.missyusa.com":1.05}},{"modelVersion":"M_10","modelWeight":9,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":2,"missyusa.com":0.1}},{"modelVersion":"M_11","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.45,"www.missyusa.com":1.5}},{"modelVersion":"M_12","modelWeight":8,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.2,"www.missyusa.com":1.7}},{"modelVersion":"M_13","modelWeight":8,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.85,"www.missyusa.com":0.75}},{"modelVersion":"M_14","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.8,"www.missyusa.com":1}},{"modelVersion":"M_15","modelWeight":1,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":1.2,"missyusa.com":1.75}},{"modelVersion":"M_16","modelWeight":2,"schema":{"fields":["domain"]},"values":{"missyusa.com":1,"www.missyusa.com":0.7}},{"modelVersion":"M_17","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.45,"www.missyusa.com":0.35}},{"modelVersion":"M_18","modelWeight":3,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.2,"www.missyusa.com":1.05}}],"skipRate":10}` + return []byte(data) + }(), + responseStatus: 200, + want: nil, + want1: 0, + }, + { + name: "Malformed response : json unmarshalling failed", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 800, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: func() []byte { + data := `{"data":nil?}` + return []byte(data) + }(), + responseStatus: 200, + want: nil, + want1: 0, + }, + { + name: "Validations failed for price floor rules response", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 700, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + responseStatus: 200, + want: nil, + want1: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) + defer mockHttpServer.Close() + ppf := PriceFloorFetcher{ + httpClient: mockHttpServer.Client(), + } + tt.args.configs.URL = mockHttpServer.URL + got, got1 := ppf.fetchAndValidate(tt.args.configs) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("fetchAndValidate() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("fetchAndValidate() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func mockFetcherInstance(config config.PriceFloors, httpClient *http.Client, metricEngine metrics.MetricsEngine) *PriceFloorFetcher { + if !config.Enabled { + return nil + } + + floorFetcher := PriceFloorFetcher{ + pool: pond.New(config.Fetcher.Worker, config.Fetcher.Capacity, pond.PanicHandler(workerPanicHandler)), + fetchQueue: make(FetchQueue, 0, 100), + fetchInProgress: make(map[string]bool), + configReceiver: make(chan fetchInfo, config.Fetcher.Capacity), + done: make(chan struct{}), + cache: freecache.NewCache(config.Fetcher.CacheSize * 1024 * 1024), + httpClient: httpClient, + time: &timeutil.RealTime{}, + metricEngine: metricEngine, + maxRetries: 10, + } + + go floorFetcher.Fetcher() + + return &floorFetcher +} + +func TestFetcherWhenRequestGetSameURLInrequest(t *testing.T) { + refetchCheckInterval = 1 + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 5, + Capacity: 10, + }, + } + fetcherInstance := mockFetcherInstance(floorConfig, mockHttpServer.Client(), &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 1, + }, + } + + for i := 0; i < 50; i++ { + fetcherInstance.Fetch(fetchConfig) + } + + assert.Never(t, func() bool { return len(fetcherInstance.fetchQueue) > 1 }, time.Duration(2*time.Second), 100*time.Millisecond, "Queue Got more than one entry") + assert.Never(t, func() bool { return len(fetcherInstance.fetchInProgress) > 1 }, time.Duration(2*time.Second), 100*time.Millisecond, "Map Got more than one entry") + +} + +func TestFetcherDataPresentInCache(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 2, + Capacity: 5, + }, + } + + fetcherInstance := mockFetcherInstance(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floor", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 5, + }, + } + var res *openrtb_ext.PriceFloorRules + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + _ = json.Unmarshal([]byte(data), &res) + fetcherInstance.SetWithExpiry("http://test.com/floor", []byte(data), fetchConfig.Fetcher.MaxAge) + + val, status := fetcherInstance.Fetch(fetchConfig) + assert.Equal(t, res, val, "Invalid value in cache or cache is empty") + assert.Equal(t, "success", status, "Floor fetch should be success") +} + +func TestFetcherDataNotPresentInCache(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 2, + Capacity: 5, + }, + } + + fetcherInstance := mockFetcherInstance(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floor", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 5, + }, + } + fetcherInstance.SetWithExpiry("http://test.com/floor", nil, fetchConfig.Fetcher.MaxAge) + + val, status := fetcherInstance.Fetch(fetchConfig) + + assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), val, "Floor data should be nil") + assert.Equal(t, "error", status, "Floor fetch should be error") +} + +func TestFetcherEntryNotPresentInCache(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 2, + Capacity: 5, + MaxRetries: 10, + }, + } + + fetcherInstance := NewPriceFloorFetcher(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floor", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 5, + }, + } + + val, status := fetcherInstance.Fetch(fetchConfig) + + assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), val, "Floor data should be nil") + assert.Equal(t, openrtb_ext.FetchInprogress, status, "Floor fetch should be error") +} + +func TestFetcherDynamicFetchDisable(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 2, + Capacity: 5, + MaxRetries: 5, + }, + } + + fetcherInstance := NewPriceFloorFetcher(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: false, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floor", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 5, + }, + } + + val, status := fetcherInstance.Fetch(fetchConfig) + + assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), val, "Floor data should be nil") + assert.Equal(t, openrtb_ext.FetchNone, status, "Floor fetch should be error") +} + +func TestPriceFloorFetcherWorker(t *testing.T) { + var floorData openrtb_ext.PriceFloorData + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + _ = json.Unmarshal(response, &floorData) + floorResp := &openrtb_ext.PriceFloorRules{ + Data: &floorData, + } + + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add(MaxAge, "5") + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + fetcherInstance := PriceFloorFetcher{ + pool: nil, + fetchQueue: nil, + fetchInProgress: nil, + configReceiver: make(chan fetchInfo, 1), + done: nil, + cache: freecache.NewCache(1 * 1024 * 1024), + httpClient: mockHttpServer.Client(), + time: &timeutil.RealTime{}, + metricEngine: &metricsConf.NilMetricsEngine{}, + maxRetries: 10, + } + defer close(fetcherInstance.configReceiver) + + fetchConfig := fetchInfo{ + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 1, + }, + } + + fetcherInstance.worker(fetchConfig) + dataInCache, _ := fetcherInstance.Get(mockHttpServer.URL) + var gotFloorData *openrtb_ext.PriceFloorRules + json.Unmarshal(dataInCache, &gotFloorData) + assert.Equal(t, floorResp, gotFloorData, "Data should be stored in cache") + + info := <-fetcherInstance.configReceiver + assert.Equal(t, true, info.refetchRequest, "Recieved request is not refetch request") + assert.Equal(t, mockHttpServer.URL, info.AccountFloorFetch.URL, "Recieved request with different url") + +} + +func TestPriceFloorFetcherWorkerRetry(t *testing.T) { + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(nil, 500)) + defer mockHttpServer.Close() + + fetcherInstance := PriceFloorFetcher{ + pool: nil, + fetchQueue: nil, + fetchInProgress: nil, + configReceiver: make(chan fetchInfo, 1), + done: nil, + cache: nil, + httpClient: mockHttpServer.Client(), + time: &timeutil.RealTime{}, + metricEngine: &metricsConf.NilMetricsEngine{}, + maxRetries: 5, + } + defer close(fetcherInstance.configReceiver) + + fetchConfig := fetchInfo{ + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 1, + }, + } + + fetcherInstance.worker(fetchConfig) + + info := <-fetcherInstance.configReceiver + assert.Equal(t, 1, info.retryCount, "Retry Count is not 1") +} + +func TestPriceFloorFetcherWorkerDefaultCacheExpiry(t *testing.T) { + var floorData openrtb_ext.PriceFloorData + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + _ = json.Unmarshal(response, &floorData) + floorResp := &openrtb_ext.PriceFloorRules{ + Data: &floorData, + } + + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + fetcherInstance := &PriceFloorFetcher{ + pool: nil, + fetchQueue: nil, + fetchInProgress: nil, + configReceiver: make(chan fetchInfo, 1), + done: nil, + cache: freecache.NewCache(1 * 1024 * 1024), + httpClient: mockHttpServer.Client(), + time: &timeutil.RealTime{}, + metricEngine: &metricsConf.NilMetricsEngine{}, + maxRetries: 5, + } + + fetchConfig := fetchInfo{ + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 1, + }, + } + + fetcherInstance.worker(fetchConfig) + dataInCache, _ := fetcherInstance.Get(mockHttpServer.URL) + var gotFloorData *openrtb_ext.PriceFloorRules + json.Unmarshal(dataInCache, &gotFloorData) + assert.Equal(t, floorResp, gotFloorData, "Data should be stored in cache") + + info := <-fetcherInstance.configReceiver + defer close(fetcherInstance.configReceiver) + assert.Equal(t, true, info.refetchRequest, "Recieved request is not refetch request") + assert.Equal(t, mockHttpServer.URL, info.AccountFloorFetch.URL, "Recieved request with different url") + +} + +func TestPriceFloorFetcherSubmit(t *testing.T) { + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + fetcherInstance := &PriceFloorFetcher{ + pool: pond.New(1, 1), + fetchQueue: make(FetchQueue, 0), + fetchInProgress: nil, + configReceiver: make(chan fetchInfo, 1), + done: make(chan struct{}), + cache: freecache.NewCache(1 * 1024 * 1024), + httpClient: mockHttpServer.Client(), + time: &timeutil.RealTime{}, + metricEngine: &metricsConf.NilMetricsEngine{}, + maxRetries: 5, + } + defer fetcherInstance.Stop() + + fetchInfo := fetchInfo{ + refetchRequest: false, + fetchTime: time.Now().Unix(), + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 2, + Period: 1, + }, + } + + fetcherInstance.submit(&fetchInfo) + + info := <-fetcherInstance.configReceiver + assert.Equal(t, true, info.refetchRequest, "Recieved request is not refetch request") + assert.Equal(t, mockHttpServer.URL, info.AccountFloorFetch.URL, "Recieved request with different url") + +} + +type testPool struct{} + +func (t *testPool) TrySubmit(task func()) bool { + return false +} + +func (t *testPool) Stop() {} + +func TestPriceFloorFetcherSubmitFailed(t *testing.T) { + fetcherInstance := &PriceFloorFetcher{ + pool: &testPool{}, + fetchQueue: make(FetchQueue, 0), + fetchInProgress: nil, + configReceiver: nil, + done: nil, + cache: nil, + } + defer fetcherInstance.pool.Stop() + + fetchInfo := fetchInfo{ + refetchRequest: false, + fetchTime: time.Now().Unix(), + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 2, + Period: 1, + }, + } + + fetcherInstance.submit(&fetchInfo) + assert.Equal(t, 1, len(fetcherInstance.fetchQueue), "Unable to submit the task") +} + +func getRandomNumber() int { + rand.Seed(time.Now().UnixNano()) + min := 1 + max := 10 + return rand.Intn(max-min+1) + min +} + +func TestFetcherWhenRequestGetDifferentURLInrequest(t *testing.T) { + refetchCheckInterval = 1 + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 5, + Capacity: 10, + MaxRetries: 5, + }, + } + fetcherInstance := mockFetcherInstance(floorConfig, mockHttpServer.Client(), &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 5, + Period: 1, + }, + } + + for i := 0; i < 50; i++ { + fetchConfig.Fetcher.URL = fmt.Sprintf("%s?id=%d", mockHttpServer.URL, getRandomNumber()) + fetcherInstance.Fetch(fetchConfig) + } + + assert.Never(t, func() bool { return len(fetcherInstance.fetchQueue) > 10 }, time.Duration(2*time.Second), 100*time.Millisecond, "Queue Got more than one entry") + assert.Never(t, func() bool { return len(fetcherInstance.fetchInProgress) > 10 }, time.Duration(2*time.Second), 100*time.Millisecond, "Map Got more than one entry") +} + +func TestFetchWhenPriceFloorsDisabled(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: false, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 5, + Capacity: 10, + }, + } + fetcherInstance := NewPriceFloorFetcher(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floors", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 5, + Period: 1, + }, + } + + data, status := fetcherInstance.Fetch(fetchConfig) + + assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), data, "floor data should be nil as fetcher instance does not created") + assert.Equal(t, openrtb_ext.FetchNone, status, "floor status should be none as fetcher instance does not created") +} diff --git a/floors/floors.go b/floors/floors.go index c1e930298ac..3fdeb4c55ae 100644 --- a/floors/floors.go +++ b/floors/floors.go @@ -4,10 +4,12 @@ import ( "errors" "math" "math/rand" + "strings" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/currency" "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" ) type Price struct { @@ -30,8 +32,7 @@ const ( // EnrichWithPriceFloors checks for floors enabled in account and request and selects floors data from dynamic fetched if present // else selects floors data from req.ext.prebid.floors and update request with selected floors details -func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, account config.Account, conversions currency.Conversions) []error { - +func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, account config.Account, conversions currency.Conversions, priceFloorFetcher FloorFetcher) []error { if bidRequestWrapper == nil || bidRequestWrapper.BidRequest == nil { return []error{errors.New("Empty bidrequest")} } @@ -40,7 +41,7 @@ func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, accoun return []error{errors.New("Floors feature is disabled at account or in the request")} } - floors, err := resolveFloors(account, bidRequestWrapper, conversions) + floors, err := resolveFloors(account, bidRequestWrapper, conversions, priceFloorFetcher) updateReqErrs := updateBidRequestWithFloors(floors, bidRequestWrapper, conversions) updateFloorsInRequest(bidRequestWrapper, floors) @@ -136,12 +137,26 @@ func isPriceFloorsEnabledForRequest(bidRequestWrapper *openrtb_ext.RequestWrappe } // resolveFloors does selection of floors fields from request data and dynamic fetched data if dynamic fetch is enabled -func resolveFloors(account config.Account, bidRequestWrapper *openrtb_ext.RequestWrapper, conversions currency.Conversions) (*openrtb_ext.PriceFloorRules, []error) { +func resolveFloors(account config.Account, bidRequestWrapper *openrtb_ext.RequestWrapper, conversions currency.Conversions, priceFloorFetcher FloorFetcher) (*openrtb_ext.PriceFloorRules, []error) { var errList []error var floorRules *openrtb_ext.PriceFloorRules reqFloor := extractFloorsFromRequest(bidRequestWrapper) - if reqFloor != nil { + if reqFloor != nil && reqFloor.Location != nil && len(reqFloor.Location.URL) > 0 { + account.PriceFloors.Fetcher.URL = reqFloor.Location.URL + } + account.PriceFloors.Fetcher.AccountID = account.ID + + var fetchResult *openrtb_ext.PriceFloorRules + var fetchStatus string + if priceFloorFetcher != nil && account.PriceFloors.UseDynamicData { + fetchResult, fetchStatus = priceFloorFetcher.Fetch(account.PriceFloors) + } + + if fetchResult != nil && fetchStatus == openrtb_ext.FetchSuccess { + mergedFloor := mergeFloors(reqFloor, fetchResult, conversions) + floorRules, errList = createFloorsFrom(mergedFloor, account, fetchStatus, openrtb_ext.FetchLocation) + } else if reqFloor != nil { floorRules, errList = createFloorsFrom(reqFloor, account, openrtb_ext.FetchNone, openrtb_ext.RequestLocation) } else { floorRules, errList = createFloorsFrom(nil, account, openrtb_ext.FetchNone, openrtb_ext.NoDataLocation) @@ -210,3 +225,83 @@ func updateFloorsInRequest(bidRequestWrapper *openrtb_ext.RequestWrapper, priceF bidRequestWrapper.RebuildRequest() } } + +// resolveFloorMin gets floorMin value from request and dynamic fetched data +func resolveFloorMin(reqFloors *openrtb_ext.PriceFloorRules, fetchFloors *openrtb_ext.PriceFloorRules, conversions currency.Conversions) Price { + var requestFloorMinCur, providerFloorMinCur string + var requestFloorMin, providerFloorMin float64 + + if reqFloors != nil { + requestFloorMin = reqFloors.FloorMin + requestFloorMinCur = reqFloors.FloorMinCur + if len(requestFloorMinCur) == 0 && reqFloors.Data != nil { + requestFloorMinCur = reqFloors.Data.Currency + } + } + + if fetchFloors != nil { + providerFloorMin = fetchFloors.FloorMin + providerFloorMinCur = fetchFloors.FloorMinCur + if len(providerFloorMinCur) == 0 && fetchFloors.Data != nil { + providerFloorMinCur = fetchFloors.Data.Currency + } + } + + if len(requestFloorMinCur) > 0 { + if requestFloorMin > 0 { + return Price{FloorMin: requestFloorMin, FloorMinCur: requestFloorMinCur} + } + + if providerFloorMin > 0 { + if strings.Compare(providerFloorMinCur, requestFloorMinCur) == 0 || len(providerFloorMinCur) == 0 { + return Price{FloorMin: providerFloorMin, FloorMinCur: requestFloorMinCur} + } + rate, err := conversions.GetRate(providerFloorMinCur, requestFloorMinCur) + if err != nil { + return Price{FloorMin: 0, FloorMinCur: requestFloorMinCur} + } + return Price{FloorMin: roundToFourDecimals(rate * providerFloorMin), FloorMinCur: requestFloorMinCur} + } + } + + if len(providerFloorMinCur) > 0 { + if providerFloorMin > 0 { + return Price{FloorMin: providerFloorMin, FloorMinCur: providerFloorMinCur} + } + if requestFloorMin > 0 { + return Price{FloorMin: requestFloorMin, FloorMinCur: providerFloorMinCur} + } + } + + return Price{FloorMin: requestFloorMin, FloorMinCur: requestFloorMinCur} + +} + +// mergeFloors does merging for floors data from request and dynamic fetch +func mergeFloors(reqFloors *openrtb_ext.PriceFloorRules, fetchFloors *openrtb_ext.PriceFloorRules, conversions currency.Conversions) *openrtb_ext.PriceFloorRules { + mergedFloors := fetchFloors.DeepCopy() + if mergedFloors.Enabled == nil { + mergedFloors.Enabled = new(bool) + } + *mergedFloors.Enabled = fetchFloors.GetEnabled() && reqFloors.GetEnabled() + + if reqFloors == nil { + return mergedFloors + } + + if reqFloors.Enforcement != nil { + mergedFloors.Enforcement = reqFloors.Enforcement.DeepCopy() + } + + floorMinPrice := resolveFloorMin(reqFloors, fetchFloors, conversions) + if floorMinPrice.FloorMin > 0 { + mergedFloors.FloorMin = floorMinPrice.FloorMin + mergedFloors.FloorMinCur = floorMinPrice.FloorMinCur + } + + if reqFloors != nil && reqFloors.Location != nil && reqFloors.Location.URL != "" { + mergedFloors.Location = ptrutil.Clone(reqFloors.Location) + } + + return mergedFloors +} diff --git a/floors/floors_test.go b/floors/floors_test.go index 56b21a1a209..047b6738cd3 100644 --- a/floors/floors_test.go +++ b/floors/floors_test.go @@ -3,6 +3,7 @@ package floors import ( "encoding/json" "errors" + "reflect" "testing" "github.com/prebid/openrtb/v19/openrtb2" @@ -50,6 +51,14 @@ func getCurrencyRates(rates map[string]map[string]float64) currency.Conversions return currency.NewRates(rates) } +type mockPriceFloorFetcher struct{} + +func (mpf *mockPriceFloorFetcher) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + return nil, openrtb_ext.FetchNone +} + +func (mpf *mockPriceFloorFetcher) Stop() {} + func TestEnrichWithPriceFloors(t *testing.T) { rates := map[string]map[string]float64{ "USD": { @@ -365,7 +374,7 @@ func TestEnrichWithPriceFloors(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - ErrList := EnrichWithPriceFloors(tc.bidRequestWrapper, tc.account, getCurrencyRates(rates)) + ErrList := EnrichWithPriceFloors(tc.bidRequestWrapper, tc.account, getCurrencyRates(rates), &mockPriceFloorFetcher{}) if tc.bidRequestWrapper != nil { assert.Equal(t, tc.bidRequestWrapper.Imp[0].BidFloor, tc.expFloorVal, tc.name) assert.Equal(t, tc.bidRequestWrapper.Imp[0].BidFloorCur, tc.expFloorCur, tc.name) @@ -393,6 +402,51 @@ func getTrue() *bool { return &trueFlag } +func getFalse() *bool { + falseFlag := false + return &falseFlag +} + +type MockFetch struct { + FakeFetch func(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) +} + +func (m *MockFetch) Stop() {} + +func (m *MockFetch) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + + if !configs.UseDynamicData { + return nil, openrtb_ext.FetchNone + } + priceFloors := openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 101", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + } + return &priceFloors, openrtb_ext.FetchSuccess +} + func TestResolveFloors(t *testing.T) { rates := map[string]map[string]float64{ "USD": { @@ -407,9 +461,149 @@ func TestResolveFloors(t *testing.T) { bidRequestWrapper *openrtb_ext.RequestWrapper account config.Account conversions currency.Conversions + fetcher FloorFetcher expErr []error expFloors *openrtb_ext.PriceFloorRules }{ + { + name: "Dynamic fetch disabled, floors from request selected", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: false, + }, + }, + fetcher: &MockFetch{}, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model 1 from req", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 5, + "*|*|*": 7, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Dynamic fetch enabled, floors from fetched selected", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: &MockFetch{}, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + FloorDeals: getTrue(), + EnforceRate: 100, + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 101", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Dynamic fetch enabled, floors formed after merging", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"floormincur":"EUR","enabled":true,"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"floormin":10.11,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: &MockFetch{}, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FloorMin: 10.11, + FloorMinCur: "EUR", + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 101", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, { name: "Dynamic fetch disabled, only enforcement object present in req.ext", bidRequestWrapper: &openrtb_ext.RequestWrapper{ @@ -427,6 +621,7 @@ func TestResolveFloors(t *testing.T) { UseDynamicData: false, }, }, + fetcher: &MockFetch{}, expFloors: &openrtb_ext.PriceFloorRules{ Enforcement: &openrtb_ext.PriceFloorEnforcement{ EnforcePBS: getTrue(), @@ -437,11 +632,129 @@ func TestResolveFloors(t *testing.T) { PriceFloorLocation: openrtb_ext.RequestLocation, }, }, + { + name: "Dynamic fetch enabled, floors from fetched selected and new URL is updated", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"floorendpoint":{"url":"http://test.com/floor"},"enabled":true}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: &MockFetch{}, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + FloorDeals: getTrue(), + EnforceRate: 100, + }, + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "http://test.com/floor", + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 101", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Dynamic Fetch Enabled but price floor fetcher is nil, floors from request is selected", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: nil, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model 1 from req", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 5, + "*|*|*": 7, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Dynamic Fetch Enabled but price floor fetcher is nil and request has no floors", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: nil, + expFloors: &openrtb_ext.PriceFloorRules{ + FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.NoDataLocation, + }, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates)) + resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates), tc.fetcher) assert.Equal(t, resolvedFloors, tc.expFloors, tc.name) }) } @@ -738,3 +1051,605 @@ func TestIsPriceFloorsEnabled(t *testing.T) { }) } } + +func TestResolveFloorMin(t *testing.T) { + rates := map[string]map[string]float64{ + "USD": { + "INR": 70, + "EUR": 0.9, + "JPY": 5.09, + }, + } + + tt := []struct { + name string + reqFloors openrtb_ext.PriceFloorRules + fetchFloors openrtb_ext.PriceFloorRules + conversions currency.Conversions + expPrice Price + }{ + { + name: "FloorsMin present in request Floors only", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "JPY", + }, + fetchFloors: openrtb_ext.PriceFloorRules{}, + expPrice: Price{FloorMin: 10, FloorMinCur: "JPY"}, + }, + { + name: "FloorsMin, FloorMinCur and data currency present in request Floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "JPY", + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + }, + }, + fetchFloors: openrtb_ext.PriceFloorRules{}, + expPrice: Price{FloorMin: 10, FloorMinCur: "JPY"}, + }, + { + name: "FloorsMin and data currency present in request Floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 10, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + }, + }, + fetchFloors: openrtb_ext.PriceFloorRules{}, + expPrice: Price{FloorMin: 10, FloorMinCur: "USD"}, + }, + { + name: "FloorsMin and FloorMinCur present in request Floors and fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "USD", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 15, + FloorMinCur: "INR", + }, + expPrice: Price{FloorMin: 10, FloorMinCur: "USD"}, + }, + { + name: "FloorsMin present fetched floors only", + reqFloors: openrtb_ext.PriceFloorRules{}, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 15, + FloorMinCur: "EUR", + }, + expPrice: Price{FloorMin: 15, FloorMinCur: "EUR"}, + }, + { + name: "FloorMinCur present in reqFloors And FloorsMin, FloorMinCur present in fetched floors (Same Currency)", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "EUR", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 15, + FloorMinCur: "EUR", + }, + expPrice: Price{FloorMin: 15, FloorMinCur: "EUR"}, + }, + { + name: "FloorMinCur present in reqFloors And FloorsMin, FloorMinCur present in fetched floors (Different Currency)", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "USD", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 15, + FloorMinCur: "EUR", + }, + expPrice: Price{FloorMin: 16.6667, FloorMinCur: "USD"}, + }, + { + name: "FloorMin present in reqFloors And FloorMinCur present in fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 11, + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "EUR", + }, + expPrice: Price{FloorMin: 11, FloorMinCur: "EUR"}, + }, + { + name: "FloorMinCur present in reqFloors And FloorMin present in fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "INR", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 12, + }, + expPrice: Price{FloorMin: 12, FloorMinCur: "INR"}, + }, + { + name: "FloorMinCur present in reqFloors And FloorMin, data currency present in fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "INR", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 1, + Data: &openrtb_ext.PriceFloorData{Currency: "USD"}, + }, + expPrice: Price{FloorMin: 70, FloorMinCur: "INR"}, + }, + { + name: "FloorMinCur present in fetched Floors And data currency present in reqFloors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 2, + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{Currency: "USD"}, + }, + expPrice: Price{FloorMin: 2, FloorMinCur: "USD"}, + }, + { + name: "Data currency and FloorMin present in fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{}, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 12, + Data: &openrtb_ext.PriceFloorData{Currency: "USD"}, + }, + expPrice: Price{FloorMin: 12, FloorMinCur: "USD"}, + }, + { + name: "Empty reqFloors And Empty fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{}, + fetchFloors: openrtb_ext.PriceFloorRules{}, + expPrice: Price{FloorMin: 0.0, FloorMinCur: ""}, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + price := resolveFloorMin(&tc.reqFloors, &tc.fetchFloors, getCurrencyRates(rates)) + if !reflect.DeepEqual(price.FloorMin, tc.expPrice.FloorMin) { + t.Errorf("Floor Value error: \nreturn:\t%v\nwant:\t%v", price.FloorMin, tc.expPrice.FloorMin) + } + if !reflect.DeepEqual(price.FloorMinCur, tc.expPrice.FloorMinCur) { + t.Errorf("Floor Currency error: \nreturn:\t%v\nwant:\t%v", price.FloorMinCur, tc.expPrice.FloorMinCur) + } + + }) + } +} + +func TestMergeFloors(t *testing.T) { + + rates := map[string]map[string]float64{ + "USD": { + "INR": 70, + "EUR": 0.9, + "JPY": 5.09, + }, + } + + type args struct { + reqFloors *openrtb_ext.PriceFloorRules + fetchFloors *openrtb_ext.PriceFloorRules + } + tests := []struct { + name string + args args + want *openrtb_ext.PriceFloorRules + }{ + { + name: "Fetched Floors are present and request Floors are empty", + args: args{ + reqFloors: nil, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Fetched Floors are present and request Floors has floors disabled", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getFalse(), + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getFalse(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Fetched Floors are present and request Floors has enforcement (enforcepbs = true)", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getTrue(), + }, + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getTrue(), + }, + }, + }, + { + name: "Fetched Floors are present and request Floors has enforcement (enforcepbs = false)", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + }, + }, + { + name: "Fetched Floors are present and request Floors has Floormin", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + }, + }, + { + name: "Fetched Floors are present and request Floors has URL", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "https://test.com/floors", + }, + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "https://test.com/floors", + }, + }, + }, + { + name: "Fetched Floors has no enable atrribute are present and request Floors has URL", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "https://test.com/floors", + }, + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "https://test.com/floors", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := mergeFloors(tt.args.reqFloors, tt.args.fetchFloors, getCurrencyRates(rates)); !reflect.DeepEqual(got, tt.want) { + t.Errorf("mergeFloors() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/floors/rule.go b/floors/rule.go index db12719c337..50c12462d28 100644 --- a/floors/rule.go +++ b/floors/rule.go @@ -38,8 +38,7 @@ const ( // getFloorCurrency returns floors currency provided in floors JSON, // if currency is not provided then defaults to USD func getFloorCurrency(floorExt *openrtb_ext.PriceFloorRules) string { - var floorCur string - + floorCur := defaultCurrency if floorExt != nil && floorExt.Data != nil { if floorExt.Data.Currency != "" { floorCur = floorExt.Data.Currency @@ -50,10 +49,6 @@ func getFloorCurrency(floorExt *openrtb_ext.PriceFloorRules) string { } } - if len(floorCur) == 0 { - floorCur = defaultCurrency - } - return floorCur } diff --git a/go.mod b/go.mod index d152a3b6614..ac394a6742c 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/IABTechLab/adscert v0.34.0 github.com/NYTimes/gziphandler v1.1.1 + github.com/alitto/pond v1.8.3 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/benbjohnson/clock v1.3.0 github.com/buger/jsonparser v1.1.1 diff --git a/go.sum b/go.sum index d05f428cef5..6331beb3287 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alitto/pond v1.8.3 h1:ydIqygCLVPqIX/USe5EaV/aSRXTRXDEI9JwuDdu+/xs= +github.com/alitto/pond v1.8.3/go.mod h1:CmvIIGd5jKLasGI3D87qDkQxjzChdKMmnXMg3fG6M6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= diff --git a/openrtb_ext/floors.go b/openrtb_ext/floors.go index 3553946cc2e..0e773c65899 100644 --- a/openrtb_ext/floors.go +++ b/openrtb_ext/floors.go @@ -1,5 +1,11 @@ package openrtb_ext +import ( + "github.com/prebid/prebid-server/v2/util/maputil" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" +) + // Defines strings for FetchStatus const ( FetchSuccess = "success" @@ -145,3 +151,59 @@ type ExtImp struct { type ImpExtPrebid struct { Floors Price `json:"floors,omitempty"` } + +func (pf *PriceFloorRules) DeepCopy() *PriceFloorRules { + if pf == nil { + return nil + } + + newRules := *pf + newRules.Enabled = ptrutil.Clone(pf.Enabled) + newRules.Skipped = ptrutil.Clone(pf.Skipped) + newRules.Location = ptrutil.Clone(pf.Location) + newRules.Data = pf.Data.DeepCopy() + newRules.Enforcement = pf.Enforcement.DeepCopy() + + return &newRules +} + +func (data *PriceFloorData) DeepCopy() *PriceFloorData { + if data == nil { + return nil + } + + newData := *data + newModelGroups := make([]PriceFloorModelGroup, len(data.ModelGroups)) + + for i := range data.ModelGroups { + var eachGroup PriceFloorModelGroup + eachGroup.Currency = data.ModelGroups[i].Currency + eachGroup.ModelWeight = ptrutil.Clone(data.ModelGroups[i].ModelWeight) + eachGroup.ModelVersion = data.ModelGroups[i].ModelVersion + eachGroup.SkipRate = data.ModelGroups[i].SkipRate + eachGroup.Values = maputil.Clone(data.ModelGroups[i].Values) + eachGroup.Default = data.ModelGroups[i].Default + eachGroup.Schema = PriceFloorSchema{ + Fields: sliceutil.Clone(data.ModelGroups[i].Schema.Fields), + Delimiter: data.ModelGroups[i].Schema.Delimiter, + } + newModelGroups[i] = eachGroup + } + newData.ModelGroups = newModelGroups + + return &newData +} + +func (enforcement *PriceFloorEnforcement) DeepCopy() *PriceFloorEnforcement { + if enforcement == nil { + return nil + } + + newEnforcement := *enforcement + newEnforcement.EnforceJS = ptrutil.Clone(enforcement.EnforceJS) + newEnforcement.EnforcePBS = ptrutil.Clone(enforcement.EnforcePBS) + newEnforcement.FloorDeals = ptrutil.Clone(enforcement.FloorDeals) + newEnforcement.BidAdjustment = ptrutil.Clone(enforcement.BidAdjustment) + + return &newEnforcement +} diff --git a/openrtb_ext/floors_test.go b/openrtb_ext/floors_test.go index 20247736768..d6c35b9f7ef 100644 --- a/openrtb_ext/floors_test.go +++ b/openrtb_ext/floors_test.go @@ -1,8 +1,10 @@ package openrtb_ext import ( + "reflect" "testing" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -218,3 +220,263 @@ func TestPriceFloorRulesGetEnabled(t *testing.T) { }) } } + +func TestPriceFloorRulesDeepCopy(t *testing.T) { + type fields struct { + FloorMin float64 + FloorMinCur string + SkipRate int + Location *PriceFloorEndpoint + Data *PriceFloorData + Enforcement *PriceFloorEnforcement + Enabled *bool + Skipped *bool + FloorProvider string + FetchStatus string + PriceFloorLocation string + } + tests := []struct { + name string + fields fields + }{ + { + name: "DeepCopy does not share same reference", + fields: fields{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "https://test/floors", + }, + Data: &PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []PriceFloorModelGroup{ + { + Currency: "INR", + ModelWeight: ptrutil.ToPtr(1), + SkipRate: 0, + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pf := &PriceFloorRules{ + FloorMin: tt.fields.FloorMin, + FloorMinCur: tt.fields.FloorMinCur, + SkipRate: tt.fields.SkipRate, + Location: tt.fields.Location, + Data: tt.fields.Data, + Enforcement: tt.fields.Enforcement, + Enabled: tt.fields.Enabled, + Skipped: tt.fields.Skipped, + FloorProvider: tt.fields.FloorProvider, + FetchStatus: tt.fields.FetchStatus, + PriceFloorLocation: tt.fields.PriceFloorLocation, + } + got := pf.DeepCopy() + if got == pf { + t.Errorf("Rules reference are same") + } + if got.Data == pf.Data { + t.Errorf("Floor data reference is same") + } + }) + } +} + +func TestFloorRulesDeepCopy(t *testing.T) { + type fields struct { + FloorMin float64 + FloorMinCur string + SkipRate int + Location *PriceFloorEndpoint + Data *PriceFloorData + Enforcement *PriceFloorEnforcement + Enabled *bool + Skipped *bool + FloorProvider string + FetchStatus string + PriceFloorLocation string + } + tests := []struct { + name string + fields fields + want *PriceFloorRules + }{ + { + name: "Copy entire floors object", + fields: fields{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "http://prebid.com/floor", + }, + Data: &PriceFloorData{ + Currency: "INR", + SkipRate: 0, + FloorsSchemaVersion: "2", + ModelTimestamp: 123, + ModelGroups: []PriceFloorModelGroup{ + { + Currency: "INR", + ModelWeight: ptrutil.ToPtr(50), + ModelVersion: "version 1", + SkipRate: 0, + Schema: PriceFloorSchema{ + Fields: []string{"a", "b", "c"}, + Delimiter: "|", + }, + Values: map[string]float64{ + "*|*|*": 20, + }, + Default: 1, + }, + }, + FloorProvider: "prebid", + }, + Enforcement: &PriceFloorEnforcement{ + EnforceJS: ptrutil.ToPtr(true), + EnforcePBS: ptrutil.ToPtr(true), + FloorDeals: ptrutil.ToPtr(true), + BidAdjustment: ptrutil.ToPtr(true), + EnforceRate: 100, + }, + Enabled: ptrutil.ToPtr(true), + Skipped: ptrutil.ToPtr(false), + FloorProvider: "Prebid", + FetchStatus: "success", + PriceFloorLocation: "fetch", + }, + want: &PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "http://prebid.com/floor", + }, + Data: &PriceFloorData{ + Currency: "INR", + SkipRate: 0, + FloorsSchemaVersion: "2", + ModelTimestamp: 123, + ModelGroups: []PriceFloorModelGroup{ + { + Currency: "INR", + ModelWeight: ptrutil.ToPtr(50), + ModelVersion: "version 1", + SkipRate: 0, + Schema: PriceFloorSchema{ + Fields: []string{"a", "b", "c"}, + Delimiter: "|", + }, + Values: map[string]float64{ + "*|*|*": 20, + }, + Default: 1, + }, + }, + FloorProvider: "prebid", + }, + Enforcement: &PriceFloorEnforcement{ + EnforceJS: ptrutil.ToPtr(true), + EnforcePBS: ptrutil.ToPtr(true), + FloorDeals: ptrutil.ToPtr(true), + BidAdjustment: ptrutil.ToPtr(true), + EnforceRate: 100, + }, + Enabled: ptrutil.ToPtr(true), + Skipped: ptrutil.ToPtr(false), + FloorProvider: "Prebid", + FetchStatus: "success", + PriceFloorLocation: "fetch", + }, + }, + { + name: "Copy entire floors object", + fields: fields{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "http://prebid.com/floor", + }, + Data: nil, + Enforcement: &PriceFloorEnforcement{ + EnforceJS: ptrutil.ToPtr(true), + EnforcePBS: ptrutil.ToPtr(true), + FloorDeals: ptrutil.ToPtr(true), + BidAdjustment: ptrutil.ToPtr(true), + EnforceRate: 100, + }, + Enabled: ptrutil.ToPtr(true), + Skipped: ptrutil.ToPtr(false), + FloorProvider: "Prebid", + FetchStatus: "success", + PriceFloorLocation: "fetch", + }, + want: &PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "http://prebid.com/floor", + }, + Data: nil, + Enforcement: &PriceFloorEnforcement{ + EnforceJS: ptrutil.ToPtr(true), + EnforcePBS: ptrutil.ToPtr(true), + FloorDeals: ptrutil.ToPtr(true), + BidAdjustment: ptrutil.ToPtr(true), + EnforceRate: 100, + }, + Enabled: ptrutil.ToPtr(true), + Skipped: ptrutil.ToPtr(false), + FloorProvider: "Prebid", + FetchStatus: "success", + PriceFloorLocation: "fetch", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pf := &PriceFloorRules{ + FloorMin: tt.fields.FloorMin, + FloorMinCur: tt.fields.FloorMinCur, + SkipRate: tt.fields.SkipRate, + Location: tt.fields.Location, + Data: tt.fields.Data, + Enforcement: tt.fields.Enforcement, + Enabled: tt.fields.Enabled, + Skipped: tt.fields.Skipped, + FloorProvider: tt.fields.FloorProvider, + FetchStatus: tt.fields.FetchStatus, + PriceFloorLocation: tt.fields.PriceFloorLocation, + } + if got := pf.DeepCopy(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("PriceFloorRules.DeepCopy() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFloorRuleDeepCopyNil(t *testing.T) { + var priceFloorRule *PriceFloorRules + got := priceFloorRule.DeepCopy() + + if got != nil { + t.Errorf("PriceFloorRules.DeepCopy() = %v, want %v", got, nil) + } +} diff --git a/router/router.go b/router/router.go index 80d972a9f5b..99fe2e5da9e 100644 --- a/router/router.go +++ b/router/router.go @@ -20,6 +20,7 @@ import ( "github.com/prebid/prebid-server/v2/errortypes" "github.com/prebid/prebid-server/v2/exchange" "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/floors" "github.com/prebid/prebid-server/v2/gdpr" "github.com/prebid/prebid-server/v2/hooks" "github.com/prebid/prebid-server/v2/macros" @@ -162,6 +163,16 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R }, } + floorFechterHttpClient := &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + MaxConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost, + MaxIdleConns: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns, + MaxIdleConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConnsPerHost, + IdleConnTimeout: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.IdleConnTimeout) * time.Second, + }, + } + if err := checkSupportedUserSyncEndpoints(cfg.BidderInfos); err != nil { return nil, err } @@ -224,10 +235,12 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R glog.Fatalf("Failed to create ads cert signer: %v", err) } + priceFloorFetcher := floors.NewPriceFloorFetcher(cfg.PriceFloors, floorFechterHttpClient, r.MetricsEngine) + tmaxAdjustments := exchange.ProcessTMaxAdjustments(cfg.TmaxAdjustments) planBuilder := hooks.NewExecutionPlanBuilder(cfg.Hooks, repo) macroReplacer := macros.NewStringIndexBasedReplacer() - theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, cfg.BidderInfos, gdprPermsBuilder, rateConvertor, categoriesFetcher, adsCertSigner, macroReplacer) + theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, cfg.BidderInfos, gdprPermsBuilder, rateConvertor, categoriesFetcher, adsCertSigner, macroReplacer, priceFloorFetcher) var uuidGenerator uuidutil.UUIDRandomGenerator openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments) if err != nil { From f9f5546542aa76672156d9220100e18dfc6996e0 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 16 Nov 2023 09:20:00 -0800 Subject: [PATCH 088/138] Modularity: Make request wrapper accessible in bidder request hook (#3096) --- exchange/bidder.go | 7 ++++++- exchange/exchange_test.go | 4 ++-- hooks/hookexecution/executor.go | 10 +++++----- hooks/hookexecution/executor_test.go | 4 ++-- hooks/hookexecution/mocks_test.go | 14 ++++++------- hooks/hookstage/bidderrequest.go | 7 +++---- hooks/hookstage/bidderrequest_mutations.go | 10 +++++----- hooks/hookstage/processedauctionrequest.go | 2 +- .../ortb2blocking/hook_bidderrequest.go | 20 +++++++++---------- modules/prebid/ortb2blocking/module_test.go | 9 ++++++--- 10 files changed, 47 insertions(+), 40 deletions(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index 610122fbbf2..3cc55acb408 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -131,7 +131,8 @@ type bidderAdapterConfig struct { } func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) { - reject := hookExecutor.ExecuteBidderRequestStage(bidderRequest.BidRequest, string(bidderRequest.BidderName)) + request := openrtb_ext.RequestWrapper{BidRequest: bidderRequest.BidRequest} + reject := hookExecutor.ExecuteBidderRequestStage(&request, string(bidderRequest.BidderName)) if reject != nil { return nil, extraBidderRespInfo{}, []error{reject} } @@ -143,6 +144,10 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde extraRespInfo extraBidderRespInfo ) + // rebuild request after modules execution + request.RebuildRequest() + bidderRequest.BidRequest = request.BidRequest + //check if real request exists for this bidder or it only has stored responses dataLen := 0 if len(bidderRequest.BidRequest.Imp) > 0 { diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index a537054cbce..25925e83566 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -5630,12 +5630,12 @@ func (e mockUpdateBidRequestHook) HandleBidderRequestHook(_ context.Context, mct c := hookstage.ChangeSet[hookstage.BidderRequestPayload]{} c.AddMutation( func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.Site.Name = "test" + payload.Request.Site.Name = "test" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "site.name", ).AddMutation( func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.Site.Domain = "test.com" + payload.Request.Site.Domain = "test.com" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "site.domain", ) diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go index dd6953fb73c..f820b79fdb2 100644 --- a/hooks/hookexecution/executor.go +++ b/hooks/hookexecution/executor.go @@ -34,7 +34,7 @@ type StageExecutor interface { ExecuteEntrypointStage(req *http.Request, body []byte) ([]byte, *RejectError) ExecuteRawAuctionStage(body []byte) ([]byte, *RejectError) ExecuteProcessedAuctionStage(req *openrtb_ext.RequestWrapper) error - ExecuteBidderRequestStage(req *openrtb2.BidRequest, bidder string) *RejectError + ExecuteBidderRequestStage(req *openrtb_ext.RequestWrapper, bidder string) *RejectError ExecuteRawBidderResponseStage(response *adapters.BidderResponse, bidder string) *RejectError ExecuteAllProcessedBidResponsesStage(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) ExecuteAuctionResponseStage(response *openrtb2.BidResponse) @@ -160,7 +160,7 @@ func (e *hookExecutor) ExecuteProcessedAuctionStage(request *openrtb_ext.Request stageName := hooks.StageProcessedAuctionRequest.String() executionCtx := e.newContext(stageName) - payload := hookstage.ProcessedAuctionRequestPayload{RequestWrapper: request} + payload := hookstage.ProcessedAuctionRequestPayload{Request: request} outcome, _, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) outcome.Entity = entityAuctionRequest @@ -177,7 +177,7 @@ func (e *hookExecutor) ExecuteProcessedAuctionStage(request *openrtb_ext.Request return reject } -func (e *hookExecutor) ExecuteBidderRequestStage(req *openrtb2.BidRequest, bidder string) *RejectError { +func (e *hookExecutor) ExecuteBidderRequestStage(req *openrtb_ext.RequestWrapper, bidder string) *RejectError { plan := e.planBuilder.PlanForBidderRequestStage(e.endpoint, e.account) if len(plan) == 0 { return nil @@ -194,7 +194,7 @@ func (e *hookExecutor) ExecuteBidderRequestStage(req *openrtb2.BidRequest, bidde stageName := hooks.StageBidderRequest.String() executionCtx := e.newContext(stageName) - payload := hookstage.BidderRequestPayload{BidRequest: req, Bidder: bidder} + payload := hookstage.BidderRequestPayload{Request: req, Bidder: bidder} outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) outcome.Entity = entity(bidder) outcome.Stage = stageName @@ -332,7 +332,7 @@ func (executor EmptyHookExecutor) ExecuteProcessedAuctionStage(_ *openrtb_ext.Re return nil } -func (executor EmptyHookExecutor) ExecuteBidderRequestStage(_ *openrtb2.BidRequest, bidder string) *RejectError { +func (executor EmptyHookExecutor) ExecuteBidderRequestStage(_ *openrtb_ext.RequestWrapper, bidder string) *RejectError { return nil } diff --git a/hooks/hookexecution/executor_test.go b/hooks/hookexecution/executor_test.go index 6f50e089b40..90fa09e394f 100644 --- a/hooks/hookexecution/executor_test.go +++ b/hooks/hookexecution/executor_test.go @@ -37,7 +37,7 @@ func TestEmptyHookExecutor(t *testing.T) { entrypointBody, entrypointRejectErr := executor.ExecuteEntrypointStage(req, body) rawAuctionBody, rawAuctionRejectErr := executor.ExecuteRawAuctionStage(body) processedAuctionRejectErr := executor.ExecuteProcessedAuctionStage(&openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}) - bidderRequestRejectErr := executor.ExecuteBidderRequestStage(bidderRequest, "bidder-name") + bidderRequestRejectErr := executor.ExecuteBidderRequestStage(&openrtb_ext.RequestWrapper{BidRequest: bidderRequest}, "bidder-name") executor.ExecuteAuctionResponseStage(&openrtb2.BidResponse{}) outcomes := executor.GetOutcomes() @@ -1171,7 +1171,7 @@ func TestExecuteBidderRequestStage(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) exec.SetAccount(test.givenAccount) - reject := exec.ExecuteBidderRequestStage(test.givenBidderRequest, bidderName) + reject := exec.ExecuteBidderRequestStage(&openrtb_ext.RequestWrapper{BidRequest: test.givenBidderRequest}, bidderName) assert.Equal(t, test.expectedReject, reject, "Unexpected stage reject.") assert.Equal(t, test.expectedBidderRequest, test.givenBidderRequest, "Incorrect bidder request.") diff --git a/hooks/hookexecution/mocks_test.go b/hooks/hookexecution/mocks_test.go index 420a44e8fe9..c47e2892cc0 100644 --- a/hooks/hookexecution/mocks_test.go +++ b/hooks/hookexecution/mocks_test.go @@ -131,7 +131,7 @@ func (e mockTimeoutHook) HandleProcessedAuctionHook(_ context.Context, _ hooksta time.Sleep(20 * time.Millisecond) c := hookstage.ChangeSet[hookstage.ProcessedAuctionRequestPayload]{} c.AddMutation(func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { - payload.RequestWrapper.User.CustomData = "some-custom-data" + payload.Request.User.CustomData = "some-custom-data" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.customData") @@ -142,7 +142,7 @@ func (e mockTimeoutHook) HandleBidderRequestHook(_ context.Context, _ hookstage. time.Sleep(20 * time.Millisecond) c := hookstage.ChangeSet[hookstage.BidderRequestPayload]{} c.AddMutation(func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.User.CustomData = "some-custom-data" + payload.Request.User.CustomData = "some-custom-data" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.customData") @@ -305,8 +305,8 @@ func (e mockUpdateBidRequestHook) HandleProcessedAuctionHook(_ context.Context, c := hookstage.ChangeSet[hookstage.ProcessedAuctionRequestPayload]{} c.AddMutation( func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { - payload.RequestWrapper.User.Yob = 2000 - userExt, err := payload.RequestWrapper.GetUserExt() + payload.Request.User.Yob = 2000 + userExt, err := payload.Request.GetUserExt() if err != nil { return payload, err } @@ -318,7 +318,7 @@ func (e mockUpdateBidRequestHook) HandleProcessedAuctionHook(_ context.Context, }, hookstage.MutationUpdate, "bidRequest", "user.yob", ).AddMutation( func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { - payload.RequestWrapper.User.Consent = "true" + payload.Request.User.Consent = "true" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.consent", ) @@ -330,12 +330,12 @@ func (e mockUpdateBidRequestHook) HandleBidderRequestHook(_ context.Context, _ h c := hookstage.ChangeSet[hookstage.BidderRequestPayload]{} c.AddMutation( func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.User.Yob = 2000 + payload.Request.User.Yob = 2000 return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.yob", ).AddMutation( func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.User.Consent = "true" + payload.Request.User.Consent = "true" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.consent", ) diff --git a/hooks/hookstage/bidderrequest.go b/hooks/hookstage/bidderrequest.go index 6609a103279..af480c5410c 100644 --- a/hooks/hookstage/bidderrequest.go +++ b/hooks/hookstage/bidderrequest.go @@ -2,8 +2,7 @@ package hookstage import ( "context" - - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // BidderRequest hooks are invoked for each bidder participating in auction. @@ -25,6 +24,6 @@ type BidderRequest interface { // distilled for the particular bidder. // Hooks are allowed to modify openrtb2.BidRequest using mutations. type BidderRequestPayload struct { - BidRequest *openrtb2.BidRequest - Bidder string + Request *openrtb_ext.RequestWrapper + Bidder string } diff --git a/hooks/hookstage/bidderrequest_mutations.go b/hooks/hookstage/bidderrequest_mutations.go index 6dfd1c6438f..746878cb4a1 100644 --- a/hooks/hookstage/bidderrequest_mutations.go +++ b/hooks/hookstage/bidderrequest_mutations.go @@ -2,9 +2,9 @@ package hookstage import ( "errors" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" ) func (c *ChangeSet[T]) BidderRequest() ChangeSetBidderRequest[T] { @@ -31,12 +31,12 @@ func (c ChangeSetBidderRequest[T]) BApp() ChangeSetBApp[T] { return ChangeSetBApp[T]{changeSetBidderRequest: c} } -func (c ChangeSetBidderRequest[T]) castPayload(p T) (*openrtb2.BidRequest, error) { +func (c ChangeSetBidderRequest[T]) castPayload(p T) (*openrtb_ext.RequestWrapper, error) { if payload, ok := any(p).(BidderRequestPayload); ok { - if payload.BidRequest == nil { - return nil, errors.New("empty BidRequest provided") + if payload.Request == nil || payload.Request.BidRequest == nil { + return nil, errors.New("payload contains a nil bid request") } - return payload.BidRequest, nil + return payload.Request, nil } return nil, errors.New("failed to cast BidderRequestPayload") } diff --git a/hooks/hookstage/processedauctionrequest.go b/hooks/hookstage/processedauctionrequest.go index 9ee06fabb2f..f420561310c 100644 --- a/hooks/hookstage/processedauctionrequest.go +++ b/hooks/hookstage/processedauctionrequest.go @@ -26,5 +26,5 @@ type ProcessedAuctionRequest interface { // ProcessedAuctionRequestPayload consists of the openrtb_ext.RequestWrapper object. // Hooks are allowed to modify openrtb_ext.RequestWrapper using mutations. type ProcessedAuctionRequestPayload struct { - RequestWrapper *openrtb_ext.RequestWrapper + Request *openrtb_ext.RequestWrapper } diff --git a/modules/prebid/ortb2blocking/hook_bidderrequest.go b/modules/prebid/ortb2blocking/hook_bidderrequest.go index 0bd990eb50c..602e75aec95 100644 --- a/modules/prebid/ortb2blocking/hook_bidderrequest.go +++ b/modules/prebid/ortb2blocking/hook_bidderrequest.go @@ -16,11 +16,11 @@ func handleBidderRequestHook( cfg config, payload hookstage.BidderRequestPayload, ) (result hookstage.HookResult[hookstage.BidderRequestPayload], err error) { - if payload.BidRequest == nil { - return result, hookexecution.NewFailure("empty BidRequest provided") + if payload.Request == nil || payload.Request.BidRequest == nil { + return result, hookexecution.NewFailure("payload contains a nil bid request") } - mediaTypes := mediaTypesFrom(payload.BidRequest) + mediaTypes := mediaTypesFrom(payload.Request.BidRequest) changeSet := hookstage.ChangeSet[hookstage.BidderRequestPayload]{} blockingAttributes := blockingAttributes{} @@ -60,7 +60,7 @@ func updateBAdv( result *hookstage.HookResult[hookstage.BidderRequestPayload], changeSet *hookstage.ChangeSet[hookstage.BidderRequestPayload], ) (err error) { - if len(payload.BidRequest.BAdv) > 0 { + if len(payload.Request.BAdv) > 0 { return nil } @@ -87,7 +87,7 @@ func updateBApp( result *hookstage.HookResult[hookstage.BidderRequestPayload], changeSet *hookstage.ChangeSet[hookstage.BidderRequestPayload], ) (err error) { - if len(payload.BidRequest.BApp) > 0 { + if len(payload.Request.BApp) > 0 { return nil } @@ -114,7 +114,7 @@ func updateBCat( result *hookstage.HookResult[hookstage.BidderRequestPayload], changeSet *hookstage.ChangeSet[hookstage.BidderRequestPayload], ) (err error) { - if len(payload.BidRequest.BCat) > 0 { + if len(payload.Request.BCat) > 0 { return nil } @@ -191,7 +191,7 @@ func updateCatTax( attributes *blockingAttributes, changeSet *hookstage.ChangeSet[hookstage.BidderRequestPayload], ) { - if payload.BidRequest.CatTax > 0 { + if payload.Request.CatTax > 0 { return } @@ -226,7 +226,7 @@ func mutationForImp( impUpdater impUpdateFunc, ) hookstage.MutationFunc[hookstage.BidderRequestPayload] { return func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - for i, imp := range payload.BidRequest.Imp { + for i, imp := range payload.Request.Imp { if values, ok := valuesByImp[imp.ID]; ok { if len(values) == 0 { continue @@ -236,7 +236,7 @@ func mutationForImp( imp.Banner = &openrtb2.Banner{} } - payload.BidRequest.Imp[i] = impUpdater(imp, values) + payload.Request.Imp[i] = impUpdater(imp, values) } } return payload, nil @@ -310,7 +310,7 @@ func findImpressionOverrides( overrides := map[string][]int{} messages := []string{} - for _, imp := range payload.BidRequest.Imp { + for _, imp := range payload.Request.Imp { // do not add override for attribute if it already exists in request if isAttrPresent(imp) { continue diff --git a/modules/prebid/ortb2blocking/module_test.go b/modules/prebid/ortb2blocking/module_test.go index b4499a4bbfb..fc5cdcc7af6 100644 --- a/modules/prebid/ortb2blocking/module_test.go +++ b/modules/prebid/ortb2blocking/module_test.go @@ -13,6 +13,7 @@ import ( "github.com/prebid/prebid-server/v2/hooks/hookexecution" "github.com/prebid/prebid-server/v2/hooks/hookstage" "github.com/prebid/prebid-server/v2/modules/moduledeps" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -497,7 +498,7 @@ func TestHandleBidderRequestHook(t *testing.T) { bidRequest: nil, expectedBidRequest: nil, expectedHookResult: hookstage.HookResult[hookstage.BidderRequestPayload]{}, - expectedError: hookexecution.NewFailure("empty BidRequest provided"), + expectedError: hookexecution.NewFailure("payload contains a nil bid request"), }, { description: "Expect baadv error if bidders and media_types not defined in config conditions", @@ -566,7 +567,8 @@ func TestHandleBidderRequestHook(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { - payload := hookstage.BidderRequestPayload{Bidder: test.bidder, BidRequest: test.bidRequest} + brw := openrtb_ext.RequestWrapper{BidRequest: test.bidRequest} + payload := hookstage.BidderRequestPayload{Bidder: test.bidder, Request: &brw} result, err := Builder(nil, moduledeps.ModuleDeps{}) assert.NoError(t, err, "Failed to build module.") @@ -590,7 +592,8 @@ func TestHandleBidderRequestHook(t *testing.T) { _, err := mut.Apply(payload) assert.NoError(t, err) } - assert.Equal(t, test.expectedBidRequest, payload.BidRequest, "Invalid BidRequest after executing BidderRequestHook.") + + assert.Equal(t, test.expectedBidRequest, payload.Request.BidRequest, "Invalid BidRequest after executing BidderRequestHook.") // reset ChangeSet not to break hookResult assertion, we validated ChangeSet separately hookResult.ChangeSet = hookstage.ChangeSet[hookstage.BidderRequestPayload]{} From b904b530658aefa13739d6afd5acb38c04c09ed4 Mon Sep 17 00:00:00 2001 From: ccorbo Date: Thu, 16 Nov 2023 12:47:54 -0500 Subject: [PATCH 089/138] fix: pass through bid.prebid.meta, currently it is being dropped (#3100) Co-authored-by: Chris Corbo --- exchange/bidder.go | 4 -- exchange/bidder_test.go | 10 ----- exchange/exchange.go | 12 ++++-- exchange/exchange_test.go | 41 ++++++++++--------- .../exchangetest/alternate-bidder-codes.json | 4 +- .../exchangetest/append-bidder-names.json | 6 +++ .../exchangetest/bid-consolidation-test.json | 9 ++++ .../bid-ext-prebid-collision.json | 3 ++ exchange/exchangetest/bid-ext.json | 3 ++ exchange/exchangetest/bid-id-invalid.json | 3 ++ exchange/exchangetest/bid-id-valid.json | 3 ++ ...e_validation_enforce_one_bid_rejected.json | 3 ++ ...bid_response_validation_warn_creative.json | 6 +++ .../bid_response_validation_warn_secure.json | 6 +++ exchange/exchangetest/debuglog_disabled.json | 6 +++ exchange/exchangetest/debuglog_enabled.json | 6 +++ .../eidpermissions-allowed-alias.json | 3 ++ ...dpermissions-allowed-case-insensitive.json | 3 ++ .../exchangetest/eidpermissions-allowed.json | 3 ++ .../exchangetest/eidpermissions-denied.json | 3 ++ .../events-bid-account-off-request-off.json | 6 +++ .../events-bid-account-off-request-on.json | 6 +++ .../events-bid-account-on-request-off.json | 6 +++ .../events-vast-account-off-request-off.json | 9 ++++ .../events-vast-account-off-request-on.json | 9 ++++ .../events-vast-account-on-request-off.json | 9 ++++ .../extra-bids-with-aliases-adaptercode.json | 6 +-- exchange/exchangetest/extra-bids.json | 4 +- ...rtydata-amp-imp-ext-one-prebid-bidder.json | 3 ++ ...ydata-imp-ext-multiple-prebid-bidders.json | 6 +++ ...stpartydata-imp-ext-one-prebid-bidder.json | 3 ++ exchange/exchangetest/fledge-with-bids.json | 3 ++ exchange/exchangetest/floors_enforcement.json | 3 ++ .../exchangetest/include-brand-category.json | 6 +++ ...epricegranularity-banner-video-native.json | 9 ++++ .../mediatypepricegranularity-native.json | 6 +++ .../exchangetest/passthrough_imp_only.json | 6 +++ .../passthrough_root_and_imp.json | 3 ++ .../exchangetest/passthrough_root_only.json | 3 ++ .../request-ext-prebid-filtering.json | 3 ++ .../request-imp-ext-prebid-filtering.json | 3 ++ .../request-multi-bidders-debug-info.json | 3 ++ .../request-multi-bidders-one-no-resp.json | 3 ++ .../targeting-cache-vast-banner.json | 3 ++ .../exchangetest/targeting-cache-vast.json | 3 ++ .../exchangetest/targeting-cache-zero.json | 3 ++ exchange/exchangetest/targeting-mobile.json | 12 ++++++ .../exchangetest/targeting-no-winners.json | 12 ++++++ .../exchangetest/targeting-only-winners.json | 12 ++++++ .../exchangetest/targeting-with-winners.json | 12 ++++++ 50 files changed, 268 insertions(+), 44 deletions(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index 3cc55acb408..ce502e53d84 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -332,10 +332,6 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if err == nil { // Conversion rate found, using it for conversion for i := 0; i < len(bidResponse.Bids); i++ { - if bidResponse.Bids[i].BidMeta == nil { - bidResponse.Bids[i].BidMeta = &openrtb_ext.ExtBidPrebidMeta{} - } - bidResponse.Bids[i].BidMeta.AdapterCode = bidderRequest.BidderName.String() bidderName := bidderRequest.BidderName if bidResponse.Bids[i].Seat != "" { diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 6db61d0d883..b2314c8c428 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -2520,7 +2520,6 @@ func TestExtraBid(t *testing.T) { DealPriority: 5, BidType: openrtb_ext.BidTypeVideo, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2532,7 +2531,6 @@ func TestExtraBid(t *testing.T) { DealPriority: 4, BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2630,7 +2628,6 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { DealPriority: 5, BidType: openrtb_ext.BidTypeVideo, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm-allowed", Currency: "USD", @@ -2642,7 +2639,6 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { DealPriority: 4, BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2740,7 +2736,6 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "PUBMATIC"}, }}, Seat: "groupm", Currency: "USD", @@ -2756,7 +2751,6 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", OriginalBidCPM: 3, - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "PUBMATIC"}, }}, Seat: "PUBMATIC", Currency: "USD", @@ -2855,7 +2849,6 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2871,7 +2864,6 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", OriginalBidCPM: 3, - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2969,7 +2961,6 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "INR", @@ -2985,7 +2976,6 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCPM: 3, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "INR", diff --git a/exchange/exchange.go b/exchange/exchange.go index bb59fe90d43..b7e26ee2a73 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -1277,7 +1277,7 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea } } - if bidExtJSON, err := makeBidExtJSON(bid.Bid.Ext, bidExtPrebid, impExtInfoMap, bid.Bid.ImpID, bid.OriginalBidCPM, bid.OriginalBidCur); err != nil { + if bidExtJSON, err := makeBidExtJSON(bid.Bid.Ext, bidExtPrebid, impExtInfoMap, bid.Bid.ImpID, bid.OriginalBidCPM, bid.OriginalBidCur, adapter); err != nil { errs = append(errs, err) } else { result = append(result, *bid.Bid) @@ -1291,7 +1291,7 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea return result, errs } -func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impExtInfoMap map[string]ImpExtInfo, impId string, originalBidCpm float64, originalBidCur string) (json.RawMessage, error) { +func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impExtInfoMap map[string]ImpExtInfo, impId string, originalBidCpm float64, originalBidCur string, adapter openrtb_ext.BidderName) (json.RawMessage, error) { var extMap map[string]interface{} if len(ext) != 0 { @@ -1320,11 +1320,17 @@ func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impEx } `json:"prebid"` }{} if err := jsonutil.Unmarshal(ext, &metaContainer); err != nil { - return nil, fmt.Errorf("error validaing response from server, %s", err) + return nil, fmt.Errorf("error validating response from server, %s", err) } prebid.Meta = &metaContainer.Prebid.Meta } + if prebid.Meta == nil { + prebid.Meta = &openrtb_ext.ExtBidPrebidMeta{} + } + + prebid.Meta.AdapterCode = adapter.String() + // ext.prebid.storedrequestattributes and ext.prebid.passthrough if impExtInfo, ok := impExtInfoMap[impId]; ok { prebid.Passthrough = impExtInfoMap[impId].Passthrough diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 25925e83566..8f7ec30d30d 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1836,7 +1836,7 @@ func TestBidResponseCurrency(t *testing.T) { Price: 9.517803, W: 300, H: 250, - Ext: json.RawMessage(`{"origbidcpm":9.517803,"prebid":{"type":"banner"}}`), + Ext: json.RawMessage(`{"origbidcpm":9.517803,"prebid":{"meta":{"adaptercode":"appnexus"},"type":"banner"}}`), }, }, }, @@ -1978,7 +1978,7 @@ func TestBidResponseImpExtInfo(t *testing.T) { []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val":1}`)} - expectedBidResponseExt := `{"origbidcpm":0,"prebid":{"type":"video","passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}` + expectedBidResponseExt := `{"origbidcpm":0,"prebid":{"meta":{"adaptercode":"appnexus"},"type":"video","passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}` actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, nil, nil, nil, true, impExtInfo, "", errList) @@ -4044,7 +4044,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta": {"brandName": "foo"}, "passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta": {"brandName": "foo","adaptercode": "adapter"}, "passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4054,7 +4054,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), nil}}, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta": {"brandName": "foo"}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta": {"brandName": "foo","adaptercode": "adapter"}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4063,7 +4063,7 @@ func TestMakeBidExtJSON(t *testing.T) { extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, origbidcpm: 0, - expectedBidExt: `{"origbidcpm": 0,"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`, expectedErrMessage: "", }, { @@ -4073,7 +4073,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: map[string]ImpExtInfo{"another_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4083,7 +4083,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, - expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4093,7 +4093,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: nil, - expectedBidExt: `{"prebid":{"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4103,7 +4103,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, - expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}, "type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4113,7 +4113,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, - expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4123,7 +4123,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, - expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4133,7 +4133,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: map[string]ImpExtInfo{}, origbidcpm: 0, origbidcur: "USD", - expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcur": "USD"}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo","adaptercode": "adapter"},"type":"banner"}, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4143,7 +4143,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: 0, origbidcur: "USD", - expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcur": "USD"}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo","adaptercode": "adapter"},"type":"banner"}, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4153,7 +4153,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo","adaptercode": "adapter"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4163,7 +4163,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo","adaptercode": "adapter"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4173,7 +4173,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: -1, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo","adaptercode":"adapter"},"type":"banner"}, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4183,7 +4183,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: 0, origbidcur: "USD", - expectedBidExt: `{"origbidcpm": 0,"prebid":{"type":"banner"}, "origbidcur": "USD"}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"type":"banner","meta":{"adaptercode":"adapter"}}, "origbidcur": "USD"}`, expectedErrMessage: "", }, //Error cases @@ -4199,13 +4199,14 @@ func TestMakeBidExtJSON(t *testing.T) { ext: json.RawMessage(`{"prebid":{"meta":{"brandId":"foo"}}}`), // brandId should be an int, but is a string in this test case extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner")}, impExtInfo: nil, - expectedErrMessage: "error validaing response from server, cannot unmarshal openrtb_ext.ExtBidPrebidMeta.BrandID: unexpected character: \xff", + expectedErrMessage: "error validating response from server, cannot unmarshal openrtb_ext.ExtBidPrebidMeta.BrandID: unexpected character: \xff", }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { - result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id", test.origbidcpm, test.origbidcur) + var adapter openrtb_ext.BidderName = "adapter" + result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id", test.origbidcpm, test.origbidcur, adapter) if test.expectedErrMessage == "" { assert.JSONEq(t, test.expectedBidExt, string(result), "Incorrect result") @@ -4249,7 +4250,7 @@ func TestStoredAuctionResponses(t *testing.T) { SeatBid: []openrtb2.SeatBid{ { Bid: []openrtb2.Bid{ - {ID: "bid_id", ImpID: "impression-id", Ext: json.RawMessage(`{"origbidcpm":0,"prebid":{"type":"video"}}`)}, + {ID: "bid_id", ImpID: "impression-id", Ext: json.RawMessage(`{"origbidcpm":0,"prebid":{"meta":{"adaptercode":"appnexus"},"type":"video"}}`)}, }, Seat: "appnexus", }, diff --git a/exchange/exchangetest/alternate-bidder-codes.json b/exchange/exchangetest/alternate-bidder-codes.json index 26a7a05d4e1..c2e6f95a5a7 100644 --- a/exchange/exchangetest/alternate-bidder-codes.json +++ b/exchange/exchangetest/alternate-bidder-codes.json @@ -206,7 +206,7 @@ "origbidcpm": 0.51, "prebid": { "meta": { - "adaptercode": "pubmatic" + "adaptercode": "groupm" }, "type": "video" } @@ -220,7 +220,7 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" + "adaptercode": "groupm" }, "type": "banner" } diff --git a/exchange/exchangetest/append-bidder-names.json b/exchange/exchangetest/append-bidder-names.json index 659045cd588..14f1181e96a 100644 --- a/exchange/exchangetest/append-bidder-names.json +++ b/exchange/exchangetest/append-bidder-names.json @@ -137,6 +137,9 @@ "origbidcpm": 0.3, "prebid": { "type": "video", + "meta": { + "adaptercode":"appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", @@ -162,6 +165,9 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/bid-consolidation-test.json b/exchange/exchangetest/bid-consolidation-test.json index b38e9d69603..61dbae5e045 100644 --- a/exchange/exchangetest/bid-consolidation-test.json +++ b/exchange/exchangetest/bid-consolidation-test.json @@ -127,6 +127,9 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + "adaptercode":"rubicon" + }, "type": "video" } } @@ -146,6 +149,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "video" } } @@ -160,6 +166,9 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/bid-ext-prebid-collision.json b/exchange/exchangetest/bid-ext-prebid-collision.json index 49e4f8a8f22..f6a2a065485 100644 --- a/exchange/exchangetest/bid-ext-prebid-collision.json +++ b/exchange/exchangetest/bid-ext-prebid-collision.json @@ -99,6 +99,9 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/bid-ext.json b/exchange/exchangetest/bid-ext.json index 69e7aba9f0c..f2416d6f2de 100644 --- a/exchange/exchangetest/bid-ext.json +++ b/exchange/exchangetest/bid-ext.json @@ -96,6 +96,9 @@ "origbidcpm": 0.3, "someField": "someValue", "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/bid-id-invalid.json b/exchange/exchangetest/bid-id-invalid.json index 9c5cb84c310..9b3c5eee245 100644 --- a/exchange/exchangetest/bid-id-invalid.json +++ b/exchange/exchangetest/bid-id-invalid.json @@ -109,6 +109,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/exchangetest/bid-id-valid.json b/exchange/exchangetest/bid-id-valid.json index b4a30640f77..caa02384025 100644 --- a/exchange/exchangetest/bid-id-valid.json +++ b/exchange/exchangetest/bid-id-valid.json @@ -109,6 +109,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "bidid": "mock_uuid", "type": "video", "targeting": { diff --git a/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json b/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json index ea48f3a966f..a6e0de460f7 100644 --- a/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json +++ b/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json @@ -173,6 +173,9 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + "adaptercode":"rubicon" + }, "type": "banner" } } diff --git a/exchange/exchangetest/bid_response_validation_warn_creative.json b/exchange/exchangetest/bid_response_validation_warn_creative.json index a170eac778e..47546683f05 100644 --- a/exchange/exchangetest/bid_response_validation_warn_creative.json +++ b/exchange/exchangetest/bid_response_validation_warn_creative.json @@ -169,6 +169,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "banner" } } @@ -188,6 +191,9 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + "adaptercode":"rubicon" + }, "type": "banner" } } diff --git a/exchange/exchangetest/bid_response_validation_warn_secure.json b/exchange/exchangetest/bid_response_validation_warn_secure.json index a6b208d2f0d..3c06f695970 100644 --- a/exchange/exchangetest/bid_response_validation_warn_secure.json +++ b/exchange/exchangetest/bid_response_validation_warn_secure.json @@ -172,6 +172,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "banner" } } @@ -192,6 +195,9 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + "adaptercode":"rubicon" + }, "type": "banner" } } diff --git a/exchange/exchangetest/debuglog_disabled.json b/exchange/exchangetest/debuglog_disabled.json index d803759d1d9..5c0e029c975 100644 --- a/exchange/exchangetest/debuglog_disabled.json +++ b/exchange/exchangetest/debuglog_disabled.json @@ -145,6 +145,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -171,6 +174,9 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json index 054737e653e..11a63f5db2e 100644 --- a/exchange/exchangetest/debuglog_enabled.json +++ b/exchange/exchangetest/debuglog_enabled.json @@ -147,6 +147,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -173,6 +176,9 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/eidpermissions-allowed-alias.json b/exchange/exchangetest/eidpermissions-allowed-alias.json index de3c0e1dca6..3dfe441567e 100644 --- a/exchange/exchangetest/eidpermissions-allowed-alias.json +++ b/exchange/exchangetest/eidpermissions-allowed-alias.json @@ -135,6 +135,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "foo" + }, "type": "video" } } diff --git a/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json b/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json index 86797501495..459bb015e25 100644 --- a/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json +++ b/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json @@ -132,6 +132,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/eidpermissions-allowed.json b/exchange/exchangetest/eidpermissions-allowed.json index 9daee27a9b6..06b2d184bf1 100644 --- a/exchange/exchangetest/eidpermissions-allowed.json +++ b/exchange/exchangetest/eidpermissions-allowed.json @@ -132,6 +132,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/eidpermissions-denied.json b/exchange/exchangetest/eidpermissions-denied.json index 04eb51bb600..72431595fe3 100644 --- a/exchange/exchangetest/eidpermissions-denied.json +++ b/exchange/exchangetest/eidpermissions-denied.json @@ -119,6 +119,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/events-bid-account-off-request-off.json b/exchange/exchangetest/events-bid-account-off-request-off.json index b15b33a8653..4ae1881b865 100644 --- a/exchange/exchangetest/events-bid-account-off-request-off.json +++ b/exchange/exchangetest/events-bid-account-off-request-off.json @@ -79,6 +79,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner" } } @@ -93,6 +96,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner" } } diff --git a/exchange/exchangetest/events-bid-account-off-request-on.json b/exchange/exchangetest/events-bid-account-off-request-on.json index 8a6b8aa7e14..20606002619 100644 --- a/exchange/exchangetest/events-bid-account-off-request-on.json +++ b/exchange/exchangetest/events-bid-account-off-request-on.json @@ -82,6 +82,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner", "events": { "imp": "http://localhost/event?t=imp&b=winning-bid&a=testaccount&bidder=appnexus&int=testIntegrationType&ts=1234567890", @@ -100,6 +103,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner", "events": { "imp": "http://localhost/event?t=imp&b=losing-bid&a=testaccount&bidder=appnexus&int=testIntegrationType&ts=1234567890", diff --git a/exchange/exchangetest/events-bid-account-on-request-off.json b/exchange/exchangetest/events-bid-account-on-request-off.json index 5a2fa6ae4d2..0447a2240e3 100644 --- a/exchange/exchangetest/events-bid-account-on-request-off.json +++ b/exchange/exchangetest/events-bid-account-on-request-off.json @@ -81,6 +81,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner", "events": { "imp": "http://localhost/event?t=imp&b=winning-bid&a=testaccount&bidder=appnexus&int=testIntegrationType&ts=1234567890", @@ -99,6 +102,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner", "events": { "imp": "http://localhost/event?t=imp&b=losing-bid&a=testaccount&bidder=appnexus&int=testIntegrationType&ts=1234567890", diff --git a/exchange/exchangetest/events-vast-account-off-request-off.json b/exchange/exchangetest/events-vast-account-off-request-off.json index a94546d2aa0..3792c0ba95b 100644 --- a/exchange/exchangetest/events-vast-account-off-request-off.json +++ b/exchange/exchangetest/events-vast-account-off-request-off.json @@ -125,6 +125,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "type": "video" } } @@ -145,6 +148,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -168,6 +174,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/events-vast-account-off-request-on.json b/exchange/exchangetest/events-vast-account-off-request-on.json index 275b8aef2ce..1a5aea4217f 100644 --- a/exchange/exchangetest/events-vast-account-off-request-on.json +++ b/exchange/exchangetest/events-vast-account-off-request-on.json @@ -131,6 +131,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "bidid": "mock_uuid", "type": "video" } @@ -154,6 +157,9 @@ "origbidcpm": 0.71, "prebid": { "bidid": "mock_uuid", + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -178,6 +184,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "bidid": "mock_uuid", "type": "video" } diff --git a/exchange/exchangetest/events-vast-account-on-request-off.json b/exchange/exchangetest/events-vast-account-on-request-off.json index e271ecf5c8f..38f407d559b 100644 --- a/exchange/exchangetest/events-vast-account-on-request-off.json +++ b/exchange/exchangetest/events-vast-account-on-request-off.json @@ -126,6 +126,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "type": "video" } } @@ -147,6 +150,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -171,6 +177,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json b/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json index b46f54c6408..8da9a29095a 100644 --- a/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json +++ b/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json @@ -185,7 +185,7 @@ "origbidcpm": 0.71, "prebid": { "meta": { - "adaptercode": "pmbidder" + "adaptercode": "groupm" }, "type": "video", "targeting": { @@ -214,7 +214,7 @@ "origbidcpm": 0.21, "prebid": { "meta": { - "adaptercode": "pmbidder" + "adaptercode": "groupm" }, "type": "video" } @@ -231,7 +231,7 @@ "origbidcpm": 0.61, "prebid": { "meta": { - "adaptercode": "pmbidder" + "adaptercode": "groupm" }, "type": "video", "targeting": { diff --git a/exchange/exchangetest/extra-bids.json b/exchange/exchangetest/extra-bids.json index 23e122593b5..edf2e60b124 100644 --- a/exchange/exchangetest/extra-bids.json +++ b/exchange/exchangetest/extra-bids.json @@ -309,7 +309,7 @@ "origbidcpm": 0.51, "prebid": { "meta": { - "adaptercode": "pubmatic" + "adaptercode": "groupm" }, "type": "video", "targeting": { @@ -333,7 +333,7 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" + "adaptercode": "groupm" }, "type": "banner" } diff --git a/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json index 627c95f9d54..22e0fb8229f 100644 --- a/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json +++ b/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json @@ -121,6 +121,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner" } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json index f15fea59d87..37d5ade5c45 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json @@ -188,6 +188,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner" } } @@ -207,6 +210,9 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + "adaptercode": "rubicon" + }, "type": "banner" } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json index 9eb6a77eed5..1b6b2043d26 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json @@ -121,6 +121,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner" } } diff --git a/exchange/exchangetest/fledge-with-bids.json b/exchange/exchangetest/fledge-with-bids.json index 0f998e69ee0..df59c0d28c7 100644 --- a/exchange/exchangetest/fledge-with-bids.json +++ b/exchange/exchangetest/fledge-with-bids.json @@ -114,6 +114,9 @@ "origbidcpm": 0.3, "someField": "someValue", "prebid": { + "meta": { + "adaptercode": "openx" + }, "type": "video" } } diff --git a/exchange/exchangetest/floors_enforcement.json b/exchange/exchangetest/floors_enforcement.json index 90d25301e65..2124bcf2aac 100644 --- a/exchange/exchangetest/floors_enforcement.json +++ b/exchange/exchangetest/floors_enforcement.json @@ -136,6 +136,9 @@ "ext": { "origbidcpm": 12, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "floors": { "floorCurrency": "USD", "floorRule": "*|*", diff --git a/exchange/exchangetest/include-brand-category.json b/exchange/exchangetest/include-brand-category.json index 9d537f74902..c0904448375 100644 --- a/exchange/exchangetest/include-brand-category.json +++ b/exchange/exchangetest/include-brand-category.json @@ -134,6 +134,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -160,6 +163,9 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/mediatypepricegranularity-banner-video-native.json b/exchange/exchangetest/mediatypepricegranularity-banner-video-native.json index 7d119b23044..4e532d1e72f 100644 --- a/exchange/exchangetest/mediatypepricegranularity-banner-video-native.json +++ b/exchange/exchangetest/mediatypepricegranularity-banner-video-native.json @@ -196,6 +196,9 @@ "ext": { "origbidcpm": 15, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner", "targeting": { "hb_bidder": "appnexus", @@ -223,6 +226,9 @@ "ext": { "origbidcpm": 18, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", @@ -255,6 +261,9 @@ "ext": { "origbidcpm": 29, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/mediatypepricegranularity-native.json b/exchange/exchangetest/mediatypepricegranularity-native.json index 57a318e1fcf..e454b500722 100644 --- a/exchange/exchangetest/mediatypepricegranularity-native.json +++ b/exchange/exchangetest/mediatypepricegranularity-native.json @@ -164,6 +164,9 @@ "ext": { "origbidcpm": 24, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "native", "targeting": { "hb_bidder": "appnexus", @@ -191,6 +194,9 @@ "ext": { "origbidcpm": 29, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/passthrough_imp_only.json b/exchange/exchangetest/passthrough_imp_only.json index b7b2270270b..bc3d8dd22de 100644 --- a/exchange/exchangetest/passthrough_imp_only.json +++ b/exchange/exchangetest/passthrough_imp_only.json @@ -144,6 +144,9 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "passthrough": { "imp_passthrough_val": 20 @@ -162,6 +165,9 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/passthrough_root_and_imp.json b/exchange/exchangetest/passthrough_root_and_imp.json index 7d4fe89b6d2..959b934f857 100644 --- a/exchange/exchangetest/passthrough_root_and_imp.json +++ b/exchange/exchangetest/passthrough_root_and_imp.json @@ -106,6 +106,9 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "passthrough": { "imp_passthrough_val": 20 diff --git a/exchange/exchangetest/passthrough_root_only.json b/exchange/exchangetest/passthrough_root_only.json index 0b444535348..254b09adc0e 100644 --- a/exchange/exchangetest/passthrough_root_only.json +++ b/exchange/exchangetest/passthrough_root_only.json @@ -101,6 +101,9 @@ "crid": "creative-1", "ext": { "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" }, "someField": "someValue", diff --git a/exchange/exchangetest/request-ext-prebid-filtering.json b/exchange/exchangetest/request-ext-prebid-filtering.json index 8e85d8edfdc..8a82f4cb8ab 100644 --- a/exchange/exchangetest/request-ext-prebid-filtering.json +++ b/exchange/exchangetest/request-ext-prebid-filtering.json @@ -174,6 +174,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/request-imp-ext-prebid-filtering.json b/exchange/exchangetest/request-imp-ext-prebid-filtering.json index fb8e0892aa5..94ac6ec057f 100644 --- a/exchange/exchangetest/request-imp-ext-prebid-filtering.json +++ b/exchange/exchangetest/request-imp-ext-prebid-filtering.json @@ -124,6 +124,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/request-multi-bidders-debug-info.json b/exchange/exchangetest/request-multi-bidders-debug-info.json index 8f41286b3eb..6eeaf58dba1 100644 --- a/exchange/exchangetest/request-multi-bidders-debug-info.json +++ b/exchange/exchangetest/request-multi-bidders-debug-info.json @@ -154,6 +154,9 @@ "ext": { "origbidcpm": 12.00, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/request-multi-bidders-one-no-resp.json b/exchange/exchangetest/request-multi-bidders-one-no-resp.json index c1fe595ad5f..fc55ebd369d 100644 --- a/exchange/exchangetest/request-multi-bidders-one-no-resp.json +++ b/exchange/exchangetest/request-multi-bidders-one-no-resp.json @@ -126,6 +126,9 @@ "ext": { "origbidcpm": 12.00, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/targeting-cache-vast-banner.json b/exchange/exchangetest/targeting-cache-vast-banner.json index 43b65e59c12..91fb5817c7a 100644 --- a/exchange/exchangetest/targeting-cache-vast-banner.json +++ b/exchange/exchangetest/targeting-cache-vast-banner.json @@ -87,6 +87,9 @@ "ext": { "origbidcpm": 0.01, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/exchangetest/targeting-cache-vast.json b/exchange/exchangetest/targeting-cache-vast.json index 2f59ba30878..1c7cb636995 100644 --- a/exchange/exchangetest/targeting-cache-vast.json +++ b/exchange/exchangetest/targeting-cache-vast.json @@ -88,6 +88,9 @@ "ext": { "origbidcpm": 0.01, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "cache": { "bids": { "cacheId": "0", diff --git a/exchange/exchangetest/targeting-cache-zero.json b/exchange/exchangetest/targeting-cache-zero.json index ea062fbe277..7f94f73f52d 100644 --- a/exchange/exchangetest/targeting-cache-zero.json +++ b/exchange/exchangetest/targeting-cache-zero.json @@ -90,6 +90,9 @@ "ext": { "origbidcpm": 0.01, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "cache": { "bids": { "cacheId": "0", diff --git a/exchange/exchangetest/targeting-mobile.json b/exchange/exchangetest/targeting-mobile.json index 914a557bca9..9cc2b1ebbc0 100644 --- a/exchange/exchangetest/targeting-mobile.json +++ b/exchange/exchangetest/targeting-mobile.json @@ -152,6 +152,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "type": "video", "targeting": { "hb_bidder_audienceNe": "audienceNetwork", @@ -179,6 +182,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -207,6 +213,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } @@ -221,6 +230,9 @@ "ext": { "origbidcpm": 0.61, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/exchangetest/targeting-no-winners.json b/exchange/exchangetest/targeting-no-winners.json index 6ab69fb4773..92526022f38 100644 --- a/exchange/exchangetest/targeting-no-winners.json +++ b/exchange/exchangetest/targeting-no-winners.json @@ -152,6 +152,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "type": "video", "targeting": { "hb_bidder_audienceNe": "audienceNetwork", @@ -178,6 +181,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder_appnexus": "appnexus", @@ -199,6 +205,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } @@ -213,6 +222,9 @@ "ext": { "origbidcpm": 0.61, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/targeting-only-winners.json b/exchange/exchangetest/targeting-only-winners.json index f9f96515452..5e6d43ce236 100644 --- a/exchange/exchangetest/targeting-only-winners.json +++ b/exchange/exchangetest/targeting-only-winners.json @@ -152,6 +152,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "type": "video" } } @@ -171,6 +174,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -192,6 +198,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } @@ -206,6 +215,9 @@ "ext": { "origbidcpm": 0.61, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/exchangetest/targeting-with-winners.json b/exchange/exchangetest/targeting-with-winners.json index d1a0f49f862..14ad8ef4b4e 100644 --- a/exchange/exchangetest/targeting-with-winners.json +++ b/exchange/exchangetest/targeting-with-winners.json @@ -152,6 +152,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "type": "video", "targeting": { "hb_bidder_audienceNe": "audienceNetwork", @@ -178,6 +181,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -204,6 +210,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } @@ -218,6 +227,9 @@ "ext": { "origbidcpm": 0.61, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", From 905436f335c9cbb20a3a827adb64a87aa6a25627 Mon Sep 17 00:00:00 2001 From: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Thu, 16 Nov 2023 09:59:53 -0800 Subject: [PATCH 090/138] Add debug flag to cookie sync endpoint (#3107) --- endpoints/cookie_sync.go | 61 +++++++++++++++--- endpoints/cookie_sync_test.go | 69 ++++++++++++++++++-- usersync/chooser.go | 35 ++++++----- usersync/chooser_test.go | 114 ++++++++++++++++++++++++++-------- 4 files changed, 226 insertions(+), 53 deletions(-) diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index bbf91330be5..f2ffb2fb4d2 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -61,7 +61,7 @@ func NewCookieSyncEndpoint( } return &cookieSyncEndpoint{ - chooser: usersync.NewChooser(syncersByBidder), + chooser: usersync.NewChooser(syncersByBidder, bidderHashSet), config: config, privacyConfig: usersyncPrivacyConfig{ gdprConfig: config.GDPR, @@ -98,17 +98,18 @@ func (c *cookieSyncEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ ht usersync.SyncHostCookie(r, cookie, &c.config.HostCookie) result := c.chooser.Choose(request, cookie) + switch result.Status { case usersync.StatusBlockedByUserOptOut: c.metrics.RecordCookieSync(metrics.CookieSyncOptOut) c.handleError(w, errCookieSyncOptOut, http.StatusUnauthorized) - case usersync.StatusBlockedByGDPR: + case usersync.StatusBlockedByPrivacy: c.metrics.RecordCookieSync(metrics.CookieSyncGDPRHostCookieBlocked) - c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, nil) + c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, nil, result.BiddersEvaluated, request.Debug) case usersync.StatusOK: c.metrics.RecordCookieSync(metrics.CookieSyncOK) c.writeSyncerMetrics(result.BiddersEvaluated) - c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, result.SyncersChosen) + c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, result.SyncersChosen, result.BiddersEvaluated, request.Debug) } } @@ -172,6 +173,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma Enabled: (request.CooperativeSync != nil && *request.CooperativeSync) || (request.CooperativeSync == nil && c.config.UserSync.Cooperative.EnabledByDefault), PriorityGroups: c.config.UserSync.PriorityGroups, }, + Debug: request.Debug, Limit: request.Limit, Privacy: usersyncPrivacy{ gdprPermissions: gdprPerms, @@ -387,9 +389,7 @@ func (c *cookieSyncEndpoint) writeSyncerMetrics(biddersEvaluated []usersync.Bidd switch bidder.Status { case usersync.StatusOK: c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncOK) - case usersync.StatusBlockedByGDPR: - c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncPrivacyBlocked) - case usersync.StatusBlockedByCCPA: + case usersync.StatusBlockedByPrivacy: c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncPrivacyBlocked) case usersync.StatusAlreadySynced: c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncAlreadySynced) @@ -399,7 +399,7 @@ func (c *cookieSyncEndpoint) writeSyncerMetrics(biddersEvaluated []usersync.Bidd } } -func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.SyncTypeFilter, co *usersync.Cookie, m macros.UserSyncPrivacy, s []usersync.SyncerChoice) { +func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.SyncTypeFilter, co *usersync.Cookie, m macros.UserSyncPrivacy, s []usersync.SyncerChoice, biddersEvaluated []usersync.BidderEvaluation, debug bool) { status := "no_cookie" if co.HasAnyLiveSyncs() { status = "ok" @@ -429,6 +429,24 @@ func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.S }) } + if debug { + biddersSeen := make(map[string]struct{}) + var debugInfo []cookieSyncResponseDebug + for _, bidderEval := range biddersEvaluated { + var debugResponse cookieSyncResponseDebug + debugResponse.Bidder = bidderEval.Bidder + if bidderEval.Status == usersync.StatusDuplicate && biddersSeen[bidderEval.Bidder] == struct{}{} { + debugResponse.Error = getDebugMessage(bidderEval.Status) + " synced as " + bidderEval.SyncerKey + debugInfo = append(debugInfo, debugResponse) + } else if bidderEval.Status != usersync.StatusOK { + debugResponse.Error = getDebugMessage(bidderEval.Status) + debugInfo = append(debugInfo, debugResponse) + } + biddersSeen[bidderEval.Bidder] = struct{}{} + } + response.Debug = debugInfo + } + c.pbsAnalytics.LogCookieSyncObject(&analytics.CookieSyncObject{ Status: http.StatusOK, BidderStatus: mapBidderStatusToAnalytics(response.BidderStatus), @@ -456,6 +474,26 @@ func mapBidderStatusToAnalytics(from []cookieSyncResponseBidder) []*analytics.Co return to } +func getDebugMessage(status usersync.Status) string { + switch status { + case usersync.StatusAlreadySynced: + return "Already in sync" + case usersync.StatusBlockedByPrivacy: + return "Rejected by privacy" + case usersync.StatusBlockedByUserOptOut: + return "Status blocked by user opt out" + case usersync.StatusDuplicate: + return "Duplicate bidder" + case usersync.StatusUnknownBidder: + return "Unsupported bidder" + case usersync.StatusUnconfiguredBidder: + return "No sync config" + case usersync.StatusTypeNotSupported: + return "Type not supported" + } + return "" +} + type cookieSyncRequest struct { Bidders []string `json:"bidders"` GDPR *int `json:"gdpr"` @@ -467,6 +505,7 @@ type cookieSyncRequest struct { CooperativeSync *bool `json:"coopSync"` FilterSettings *cookieSyncRequestFilterSettings `json:"filterSettings"` Account string `json:"account"` + Debug bool `json:"debug"` } type cookieSyncRequestFilterSettings struct { @@ -482,6 +521,7 @@ type cookieSyncRequestFilter struct { type cookieSyncResponse struct { Status string `json:"status"` BidderStatus []cookieSyncResponseBidder `json:"bidder_status"` + Debug []cookieSyncResponseDebug `json:"debug,omitempty"` } type cookieSyncResponseBidder struct { @@ -496,6 +536,11 @@ type cookieSyncResponseSync struct { SupportCORS bool `json:"supportCORS,omitempty"` } +type cookieSyncResponseDebug struct { + Bidder string `json:"bidder"` + Error string `json:"error,omitempty"` +} + type usersyncPrivacyConfig struct { gdprConfig config.GDPR gdprPermissionsBuilder gdpr.PermissionsBuilder diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 956123006ca..182a6666436 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -45,6 +45,7 @@ func TestNewCookieSyncEndpoint(t *testing.T) { analytics = MockAnalyticsRunner{} fetcher = FakeAccountsFetcher{} bidders = map[string]openrtb_ext.BidderName{"bidderA": openrtb_ext.BidderName("bidderA"), "bidderB": openrtb_ext.BidderName("bidderB")} + biddersKnown = map[string]struct{}{"bidderA": {}, "bidderB": {}} ) endpoint := NewCookieSyncEndpoint( @@ -65,7 +66,7 @@ func TestNewCookieSyncEndpoint(t *testing.T) { result := endpoint.(*cookieSyncEndpoint) expected := &cookieSyncEndpoint{ - chooser: usersync.NewChooser(syncersByBidder), + chooser: usersync.NewChooser(syncersByBidder, biddersKnown), config: &config.Configuration{ UserSync: configUserSync, HostCookie: configHostCookie, @@ -231,7 +232,7 @@ func TestCookieSyncHandle(t *testing.T) { givenCookie: cookieWithSyncs, givenBody: strings.NewReader(`{}`), givenChooserResult: usersync.Result{ - Status: usersync.StatusBlockedByGDPR, + Status: usersync.StatusBlockedByPrivacy, BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, }, @@ -249,6 +250,38 @@ func TestCookieSyncHandle(t *testing.T) { a.On("LogCookieSyncObject", &expected).Once() }, }, + { + description: "Debug Check", + givenCookie: cookieWithSyncs, + givenBody: strings.NewReader(`{"debug": true}`), + givenChooserResult: usersync.Result{ + Status: usersync.StatusOK, + BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusAlreadySynced}}, + SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, + }, + expectedStatusCode: 200, + expectedBody: `{"status":"ok","bidder_status":[` + + `{"bidder":"a","no_cookie":true,"usersync":{"url":"aURL","type":"redirect","supportCORS":true}}` + + `],"debug":[{"bidder":"a","error":"Already in sync"}]}` + "\n", + setMetricsExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordCookieSync", metrics.CookieSyncOK).Once() + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncAlreadySynced).Once() + }, + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { + expected := analytics.CookieSyncObject{ + Status: 200, + Errors: nil, + BidderStatus: []*analytics.CookieSyncBidder{ + { + BidderCode: "a", + NoCookie: true, + UsersyncInfo: &analytics.UsersyncInfo{URL: "aURL", Type: "redirect", SupportCORS: true}, + }, + }, + } + a.On("LogCookieSyncObject", &expected).Once() + }, + }, } for _, test := range testCases { @@ -1508,14 +1541,14 @@ func TestCookieSyncWriteBidderMetrics(t *testing.T) { }, { description: "One - Blocked By GDPR", - given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByGDPR}}, + given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByPrivacy}}, setExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncPrivacyBlocked).Once() }, }, { description: "One - Blocked By CCPA", - given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByCCPA}}, + given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByPrivacy}}, setExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncPrivacyBlocked).Once() }, @@ -1580,10 +1613,21 @@ func TestCookieSyncHandleResponse(t *testing.T) { syncerWithError := MockSyncer{} syncerWithError.On("GetSync", syncTypeExpected, privacyMacros).Return(syncWithError, errors.New("anyError")).Maybe() + bidderEvalForDebug := []usersync.BidderEvaluation{ + {Bidder: "Bidder1", Status: usersync.StatusAlreadySynced}, + {Bidder: "Bidder2", Status: usersync.StatusUnknownBidder}, + {Bidder: "Bidder3", Status: usersync.StatusUnconfiguredBidder}, + {Bidder: "Bidder4", Status: usersync.StatusBlockedByPrivacy}, + {Bidder: "Bidder5", Status: usersync.StatusTypeNotSupported}, + {Bidder: "Bidder6", Status: usersync.StatusBlockedByUserOptOut}, + {Bidder: "BidderA", Status: usersync.StatusDuplicate, SyncerKey: "syncerB"}, + } + testCases := []struct { description string givenCookieHasSyncs bool givenSyncersChosen []usersync.SyncerChoice + givenDebug bool expectedJSON string expectedAnalytics analytics.CookieSyncObject }{ @@ -1661,6 +1705,14 @@ func TestCookieSyncHandleResponse(t *testing.T) { expectedJSON: `{"status":"no_cookie","bidder_status":[]}` + "\n", expectedAnalytics: analytics.CookieSyncObject{Status: 200, BidderStatus: []*analytics.CookieSyncBidder{}}, }, + { + description: "Debug is true, should see all rejected bidder eval statuses in response", + givenCookieHasSyncs: true, + givenDebug: true, + givenSyncersChosen: []usersync.SyncerChoice{}, + expectedJSON: `{"status":"ok","bidder_status":[],"debug":[{"bidder":"Bidder1","error":"Already in sync"},{"bidder":"Bidder2","error":"Unsupported bidder"},{"bidder":"Bidder3","error":"No sync config"},{"bidder":"Bidder4","error":"Rejected by privacy"},{"bidder":"Bidder5","error":"Type not supported"},{"bidder":"Bidder6","error":"Status blocked by user opt out"},{"bidder":"BidderA","error":"Duplicate bidder synced as syncerB"}]}` + "\n", + expectedAnalytics: analytics.CookieSyncObject{Status: 200, BidderStatus: []*analytics.CookieSyncBidder{}}, + }, } for _, test := range testCases { @@ -1676,7 +1728,14 @@ func TestCookieSyncHandleResponse(t *testing.T) { writer := httptest.NewRecorder() endpoint := cookieSyncEndpoint{pbsAnalytics: &mockAnalytics} - endpoint.handleResponse(writer, syncTypeFilter, cookie, privacyMacros, test.givenSyncersChosen) + + var bidderEval []usersync.BidderEvaluation + if test.givenDebug { + bidderEval = bidderEvalForDebug + } else { + bidderEval = []usersync.BidderEvaluation{} + } + endpoint.handleResponse(writer, syncTypeFilter, cookie, privacyMacros, test.givenSyncersChosen, bidderEval, test.givenDebug) if assert.Equal(t, writer.Code, http.StatusOK, test.description+":http_status") { assert.Equal(t, writer.Header().Get("Content-Type"), "application/json; charset=utf-8", test.description+":http_header") diff --git a/usersync/chooser.go b/usersync/chooser.go index 3b97359e1ce..0615be56c3d 100644 --- a/usersync/chooser.go +++ b/usersync/chooser.go @@ -14,8 +14,9 @@ type Chooser interface { } // NewChooser returns a new instance of the standard chooser implementation. -func NewChooser(bidderSyncerLookup map[string]Syncer) Chooser { +func NewChooser(bidderSyncerLookup map[string]Syncer, biddersKnown map[string]struct{}) Chooser { bidders := make([]string, 0, len(bidderSyncerLookup)) + for k := range bidderSyncerLookup { bidders = append(bidders, k) } @@ -25,6 +26,7 @@ func NewChooser(bidderSyncerLookup map[string]Syncer) Chooser { biddersAvailable: bidders, bidderChooser: standardBidderChooser{shuffler: randomShuffler{}}, normalizeValidBidderName: openrtb_ext.NormalizeBidderName, + biddersKnown: biddersKnown, } } @@ -35,6 +37,7 @@ type Request struct { Limit int Privacy Privacy SyncTypeFilter SyncTypeFilter + Debug bool } // Cooperative specifies the settings for cooperative syncing for a given request, where bidders @@ -74,13 +77,6 @@ const ( // StatusBlockedByUserOptOut specifies a user's cookie explicitly signals an opt-out. StatusBlockedByUserOptOut - // StatusBlockedByGDPR specifies a user's GDPR TCF consent explicitly forbids host cookies - // or specific bidder syncing. - StatusBlockedByGDPR - - // StatusBlockedByCCPA specifies a user's CCPA consent explicitly forbids bidder syncing. - StatusBlockedByCCPA - // StatusAlreadySynced specifies a user's cookie has an existing non-expired sync for a specific bidder. StatusAlreadySynced @@ -95,6 +91,9 @@ const ( // StatusBlockedByPrivacy specifies a bidder sync url is not allowed by privacy activities StatusBlockedByPrivacy + + // StatusUnconfiguredBidder refers to a bidder who hasn't been configured to have a syncer key, but is known by Prebid Server + StatusUnconfiguredBidder ) // Privacy determines which privacy policies will be enforced for a user sync request. @@ -111,6 +110,7 @@ type standardChooser struct { biddersAvailable []string bidderChooser bidderChooser normalizeValidBidderName func(name string) (openrtb_ext.BidderName, bool) + biddersKnown map[string]struct{} } // Choose randomly selects user syncers which are permitted by the user's privacy settings and @@ -121,10 +121,11 @@ func (c standardChooser) Choose(request Request, cookie *Cookie) Result { } if !request.Privacy.GDPRAllowsHostCookie() { - return Result{Status: StatusBlockedByGDPR} + return Result{Status: StatusBlockedByPrivacy} } syncersSeen := make(map[string]struct{}) + biddersSeen := make(map[string]struct{}) limitDisabled := request.Limit <= 0 biddersEvaluated := make([]BidderEvaluation, 0) @@ -132,12 +133,16 @@ func (c standardChooser) Choose(request Request, cookie *Cookie) Result { bidders := c.bidderChooser.choose(request.Bidders, c.biddersAvailable, request.Cooperative) for i := 0; i < len(bidders) && (limitDisabled || len(syncersChosen) < request.Limit); i++ { + if _, ok := biddersSeen[bidders[i]]; ok { + continue + } syncer, evaluation := c.evaluate(bidders[i], syncersSeen, request.SyncTypeFilter, request.Privacy, cookie) biddersEvaluated = append(biddersEvaluated, evaluation) if evaluation.Status == StatusOK { syncersChosen = append(syncersChosen, SyncerChoice{Bidder: bidders[i], Syncer: syncer}) } + biddersSeen[bidders[i]] = struct{}{} } return Result{Status: StatusOK, BiddersEvaluated: biddersEvaluated, SyncersChosen: syncersChosen} @@ -151,7 +156,11 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} syncer, exists := c.bidderSyncerLookup[bidderNormalized.String()] if !exists { - return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} + if _, ok := c.biddersKnown[bidder]; !ok { + return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} + } else { + return nil, BidderEvaluation{Status: StatusUnconfiguredBidder, Bidder: bidder} + } } _, seen := syncersSeen[syncer.Key()] @@ -174,11 +183,7 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} } if !privacy.GDPRAllowsBidderSync(bidderNormalized.String()) { - return nil, BidderEvaluation{Status: StatusBlockedByGDPR, Bidder: bidder, SyncerKey: syncer.Key()} - } - - if !privacy.CCPAAllowsBidderSync(bidderNormalized.String()) { - return nil, BidderEvaluation{Status: StatusBlockedByCCPA, Bidder: bidder, SyncerKey: syncer.Key()} + return nil, BidderEvaluation{Status: StatusBlockedByPrivacy, Bidder: bidder, SyncerKey: syncer.Key()} } return syncer, BidderEvaluation{Status: StatusOK, Bidder: bidder, SyncerKey: syncer.Key()} diff --git a/usersync/chooser_test.go b/usersync/chooser_test.go index e73122b784a..4e5d4acdc9c 100644 --- a/usersync/chooser_test.go +++ b/usersync/chooser_test.go @@ -40,7 +40,7 @@ func TestNewChooser(t *testing.T) { } for _, test := range testCases { - chooser, _ := NewChooser(test.bidderSyncerLookup).(standardChooser) + chooser, _ := NewChooser(test.bidderSyncerLookup, make(map[string]struct{})).(standardChooser) assert.ElementsMatch(t, test.expectedBiddersAvailable, chooser.biddersAvailable, test.description) } } @@ -49,12 +49,18 @@ func TestChooserChoose(t *testing.T) { fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: true} fakeSyncerC := fakeSyncer{key: "keyC", supportsIFrame: false} - bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "c": fakeSyncerC, "appnexus": fakeSyncerA} + + duplicateSyncer := fakeSyncer{key: "syncerForDuplicateTest", supportsIFrame: true} + bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "c": fakeSyncerC, "appnexus": fakeSyncerA, "d": duplicateSyncer, "e": duplicateSyncer} + biddersKnown := map[string]struct{}{"a": {}, "b": {}, "c": {}} + normalizedBidderNamesLookup := func(name string) (openrtb_ext.BidderName, bool) { return openrtb_ext.BidderName(name), true } + syncerChoiceA := SyncerChoice{Bidder: "a", Syncer: fakeSyncerA} syncerChoiceB := SyncerChoice{Bidder: "b", Syncer: fakeSyncerB} + syncTypeFilter := SyncTypeFilter{ IFrame: NewUniformBidderFilter(BidderFilterModeInclude), Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} @@ -94,7 +100,7 @@ func TestChooserChoose(t *testing.T) { givenCookie: Cookie{}, bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ - Status: StatusBlockedByGDPR, + Status: StatusBlockedByPrivacy, BiddersEvaluated: nil, SyncersChosen: nil, }, @@ -144,6 +150,21 @@ func TestChooserChoose(t *testing.T) { SyncersChosen: []SyncerChoice{}, }, }, + { + description: "One Bidder - No Sync - Unknown", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"unknown"}, + givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "unknown", Status: StatusUnknownBidder}}, + SyncersChosen: []SyncerChoice{}, + }, + }, { description: "Many Bidders - All Sync - Limit Disabled With 0", givenRequest: Request{ @@ -219,6 +240,59 @@ func TestChooserChoose(t *testing.T) { SyncersChosen: []SyncerChoice{syncerChoiceA}, }, }, + { + description: "Chosen bidders have duplicate syncer keys, the one that comes first should be labled OK", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"d", "e"}, + givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{ + {Bidder: "d", SyncerKey: "syncerForDuplicateTest", Status: StatusOK}, + {Bidder: "e", SyncerKey: "syncerForDuplicateTest", Status: StatusDuplicate}, + }, + SyncersChosen: []SyncerChoice{{Bidder: "d", Syncer: duplicateSyncer}}, + }, + }, + { + description: "Chosen bidders have duplicate syncer keys, the one that comes first should be labled OK (reverse)", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"e", "d"}, + givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{ + {Bidder: "e", SyncerKey: "syncerForDuplicateTest", Status: StatusOK}, + {Bidder: "d", SyncerKey: "syncerForDuplicateTest", Status: StatusDuplicate}, + }, + SyncersChosen: []SyncerChoice{{Bidder: "e", Syncer: duplicateSyncer}}, + }, + }, + { + description: "Same bidder name, no duplicate warning", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a", "a"}, + givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{ + {Bidder: "a", SyncerKey: fakeSyncerA.key, Status: StatusOK}, + }, + SyncersChosen: []SyncerChoice{{Bidder: "a", Syncer: fakeSyncerA}}, + }, + }, { description: "Unknown Bidder", givenRequest: Request{ @@ -286,6 +360,7 @@ func TestChooserChoose(t *testing.T) { biddersAvailable: biddersAvailable, bidderChooser: mockBidderChooser, normalizeValidBidderName: test.bidderNamesLookup, + biddersKnown: biddersKnown, } result := chooser.Choose(test.givenRequest, &test.givenCookie) @@ -296,7 +371,10 @@ func TestChooserChoose(t *testing.T) { func TestChooserEvaluate(t *testing.T) { fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: false} + + biddersKnown := map[string]struct{}{"a": {}, "b": {}, "unconfigured": {}} bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "appnexus": fakeSyncerA, "suntContent": fakeSyncerA} + syncTypeFilter := SyncTypeFilter{ IFrame: NewUniformBidderFilter(BidderFilterModeInclude), Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} @@ -401,19 +479,7 @@ func TestChooserEvaluate(t *testing.T) { givenSyncTypeFilter: syncTypeFilter, normalizedBidderNamesLookup: normalizedBidderNamesLookup, expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByGDPR}, - }, - { - description: "Blocked By CCPA", - givenBidder: "a", - normalisedBidderName: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: false, activityAllowUserSync: true}, - givenCookie: cookieNeedsSync, - givenSyncTypeFilter: syncTypeFilter, - normalizedBidderNamesLookup: normalizedBidderNamesLookup, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByCCPA}, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, }, { description: "Blocked By activity control", @@ -463,25 +529,23 @@ func TestChooserEvaluate(t *testing.T) { givenSyncTypeFilter: syncTypeFilter, normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusBlockedByGDPR}, + expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, }, { - description: "Case Insensitivity Check For Blocked By CCPA", - givenBidder: "AppNexus", - normalisedBidderName: "appnexus", + description: "Unconfigured Bidder", + givenBidder: "unconfigured", + normalizedBidderNamesLookup: normalizedBidderNamesLookup, givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: false, activityAllowUserSync: true}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, givenCookie: cookieNeedsSync, - givenSyncTypeFilter: syncTypeFilter, - normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusBlockedByCCPA}, + expectedEvaluation: BidderEvaluation{Bidder: "unconfigured", Status: StatusUnconfiguredBidder}, }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { - chooser, _ := NewChooser(bidderSyncerLookup).(standardChooser) + chooser, _ := NewChooser(bidderSyncerLookup, biddersKnown).(standardChooser) chooser.normalizeValidBidderName = test.normalizedBidderNamesLookup sync, evaluation := chooser.evaluate(test.givenBidder, test.givenSyncersSeen, test.givenSyncTypeFilter, &test.givenPrivacy, &test.givenCookie) From 6a20c0e1945df1a46690a26aaf79183b321318fb Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 16 Nov 2023 10:16:36 -0800 Subject: [PATCH 091/138] Privacy scrubber refactoring (#3108) --- exchange/utils.go | 82 ++-- exchange/utils_test.go | 324 +++++++++----- ortb/clone.go | 134 ++++++ ortb/clone_test.go | 378 ++++++++++++++++ privacy/enforcement.go | 92 ---- privacy/enforcement_test.go | 393 ----------------- privacy/scrubber.go | 315 +++++--------- privacy/scrubber_test.go | 840 +++++++++++++----------------------- 8 files changed, 1186 insertions(+), 1372 deletions(-) delete mode 100644 privacy/enforcement.go delete mode 100644 privacy/enforcement_test.go diff --git a/exchange/utils.go b/exchange/utils.go index 150fac3ca53..dfe1fe448ba 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -21,6 +21,7 @@ import ( "github.com/prebid/prebid-server/v2/gdpr" "github.com/prebid/prebid-server/v2/metrics" "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/ortb" "github.com/prebid/prebid-server/v2/privacy" "github.com/prebid/prebid-server/v2/privacy/ccpa" "github.com/prebid/prebid-server/v2/privacy/lmt" @@ -153,11 +154,6 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, // bidder level privacy policies for _, bidderRequest := range allBidderRequests { - privacyEnforcement := privacy.Enforcement{ - COPPA: coppa, - LMT: lmt, - } - // fetchBids activity scopedName := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderRequest.BidderName.String()} fetchBidsActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityFetchBids, scopedName, privacy.NewRequestFromBidRequest(*req)) @@ -180,46 +176,56 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, } } + ipConf := privacy.IPConf{IPV6: auctionReq.Account.Privacy.IPv6Config, IPV4: auctionReq.Account.Privacy.IPv4Config} + + // FPD should be applied before policies, otherwise it overrides policies and activities restricted data + applyFPD(auctionReq.FirstPartyData, bidderRequest) + + reqWrapper := cloneBidderReq(bidderRequest.BidRequest) + passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scopedName, privacy.NewRequestFromBidRequest(*req)) if !passIDActivityAllowed { - privacyEnforcement.UFPD = true + //UFPD + privacy.ScrubUserFPD(reqWrapper) } else { // run existing policies (GDPR, CCPA, COPPA, LMT) // potentially block passing IDs based on GDPR - if gdprEnforced { - if gdprErr == nil { - privacyEnforcement.GDPRID = !auctionPermissions.PassID - } else { - privacyEnforcement.GDPRID = true - } + if gdprEnforced && (gdprErr != nil || !auctionPermissions.PassID) { + privacy.ScrubGdprID(reqWrapper) } // potentially block passing IDs based on CCPA - privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) + if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) { + privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false) + } } passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scopedName, privacy.NewRequestFromBidRequest(*req)) if !passGeoActivityAllowed { - privacyEnforcement.PreciseGeo = true + privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf) } else { // run existing policies (GDPR, CCPA, COPPA, LMT) // potentially block passing geo based on GDPR - if gdprEnforced { - if gdprErr == nil { - privacyEnforcement.GDPRGeo = !auctionPermissions.PassGeo - } else { - privacyEnforcement.GDPRGeo = true - } + if gdprEnforced && (gdprErr != nil || !auctionPermissions.PassGeo) { + privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf) } // potentially block passing geo based on CCPA - privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) + if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) { + privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false) + } + } + if lmt || coppa { + privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", coppa) } - applyFPD(auctionReq.FirstPartyData, bidderRequest) + passTIDAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitTIDs, scopedName, privacy.NewRequestFromBidRequest(*req)) + if !passTIDAllowed { + privacy.ScrubTID(reqWrapper) + } - privacyEnforcement.TID = !auctionReq.Activities.Allow(privacy.ActivityTransmitTIDs, scopedName, privacy.NewRequestFromBidRequest(*req)) + reqWrapper.RebuildRequest() + bidderRequest.BidRequest = reqWrapper.BidRequest - privacyEnforcement.Apply(bidderRequest.BidRequest, auctionReq.Account.Privacy) allowedBidderRequests = append(allowedBidderRequests, bidderRequest) // GPP downgrade: always downgrade unless we can confirm GPP is supported @@ -232,6 +238,34 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, return } +// cloneBidderReq - clones bidder request and replaces req.User and req.Device with new copies +func cloneBidderReq(req *openrtb2.BidRequest) *openrtb_ext.RequestWrapper { + + // bidder request may be modified differently per bidder based on privacy configs + // new request should be created for each bidder request + // pointer fields like User and Device should be cloned and set back to the request copy + var newReq *openrtb2.BidRequest + newReq = ptrutil.Clone(req) + + if req.User != nil { + userCopy := ortb.CloneUser(req.User) + newReq.User = userCopy + } + + if req.Device != nil { + deviceCopy := ortb.CloneDevice(req.Device) + newReq.Device = deviceCopy + } + + if req.Source != nil { + sourceCopy := ortb.CloneSource(req.Source) + newReq.Source = sourceCopy + } + + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: newReq} + return reqWrapper +} + func shouldSetLegacyPrivacy(bidderInfo config.BidderInfos, bidder string) bool { binfo, defined := bidderInfo[bidder] diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 03184b7402d..1e5213ab0b1 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -26,6 +26,8 @@ import ( "github.com/stretchr/testify/mock" ) +const deviceUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36" + // permissionsMock mocks the Permissions interface for tests type permissionsMock struct { allowAllBidders bool @@ -2279,6 +2281,7 @@ func TestCleanOpenRTBRequestsWithOpenRTBDowngrade(t *testing.T) { bidReq.User.ID = "" bidReq.User.BuyerUID = "" bidReq.User.Yob = 0 + bidReq.User.Gender = "" bidReq.User.Geo = &openrtb2.Geo{Lat: 123.46} downgradedRegs := *bidReq.Regs @@ -2536,7 +2539,7 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { }, Device: &openrtb2.Device{ DIDMD5: "some device ID hash", - UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + UA: deviceUA, IFA: "ifa", IP: "132.173.230.74", DNT: &dnt, @@ -2580,11 +2583,17 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { }, }, Device: &openrtb2.Device{ - DIDMD5: "some device ID hash", - UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", - IFA: "ifa", + UA: deviceUA, IP: "132.173.230.74", Language: "EN", + DIDMD5: "DIDMD5", + IFA: "IFA", + DIDSHA1: "DIDSHA1", + DPIDMD5: "DPIDMD5", + DPIDSHA1: "DPIDSHA1", + MACMD5: "MACMD5", + MACSHA1: "MACSHA1", + Geo: &openrtb2.Geo{Lat: 123.456, Lon: 11.278}, }, Source: &openrtb2.Source{ TID: "testTID", @@ -2593,8 +2602,13 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { ID: "our-id", BuyerUID: "their-id", Yob: 1982, - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{Lat: 123.456}, + Gender: "test", + Ext: json.RawMessage(`{"data": 1, "test": 2}`), + Geo: &openrtb2.Geo{Lat: 123.456, Lon: 11.278}, + EIDs: []openrtb2.EID{ + {Source: "eids-source"}, + }, + Data: []openrtb2.Data{{ID: "data-id"}}, }, Imp: []openrtb2.Imp{{ BidFloor: 100, @@ -2624,7 +2638,7 @@ func newBidRequestWithBidderParams(t *testing.T) *openrtb2.BidRequest { }, Device: &openrtb2.Device{ DIDMD5: "some device ID hash", - UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + UA: deviceUA, IFA: "ifa", IP: "132.173.230.74", Language: "EN", @@ -4466,126 +4480,210 @@ func TestGetMediaTypeForBid(t *testing.T) { } func TestCleanOpenRTBRequestsActivities(t *testing.T) { + expectedUserDefault := openrtb2.User{ + ID: "our-id", + BuyerUID: "their-id", + Yob: 1982, + Gender: "test", + Ext: json.RawMessage(`{"data": 1, "test": 2}`), + Geo: &openrtb2.Geo{Lat: 123.456, Lon: 11.278}, + EIDs: []openrtb2.EID{ + {Source: "eids-source"}, + }, + Data: []openrtb2.Data{{ID: "data-id"}}, + } + expectedDeviceDefault := openrtb2.Device{ + UA: deviceUA, + IP: "132.173.230.74", + Language: "EN", + DIDMD5: "DIDMD5", + IFA: "IFA", + DIDSHA1: "DIDSHA1", + DPIDMD5: "DPIDMD5", + DPIDSHA1: "DPIDSHA1", + MACMD5: "MACMD5", + MACSHA1: "MACSHA1", + Geo: &openrtb2.Geo{Lat: 123.456, Lon: 11.278}, + } + + expectedSourceDefault := openrtb2.Source{ + TID: "testTID", + } + testCases := []struct { - name string - req *openrtb2.BidRequest - privacyConfig config.AccountPrivacy - componentName string - allow bool - expectedReqNumber int - expectedUserYOB int64 - expectedUserLat float64 - expectedDeviceDIDMD5 string - expectedSourceTID string + name string + req *openrtb2.BidRequest + privacyConfig config.AccountPrivacy + componentName string + allow bool + expectedReqNumber int + expectedUser openrtb2.User + expectedDevice openrtb2.Device + expectedSource openrtb2.Source + expectedImpExt json.RawMessage }{ { - name: "fetch_bids_request_with_one_bidder_allowed", - req: newBidRequest(t), - privacyConfig: getFetchBidsActivityConfig("appnexus", true), - expectedReqNumber: 1, - expectedUserYOB: 1982, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "testTID", - }, - { - name: "fetch_bids_request_with_one_bidder_not_allowed", - req: newBidRequest(t), - privacyConfig: getFetchBidsActivityConfig("appnexus", false), - expectedReqNumber: 0, - expectedUserYOB: 1982, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "testTID", - }, - { - name: "transmit_ufpd_allowed", - req: newBidRequest(t), - privacyConfig: getTransmitUFPDActivityConfig("appnexus", true), - expectedReqNumber: 1, - expectedUserYOB: 1982, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "testTID", - }, - { - name: "transmit_ufpd_deny", - req: newBidRequest(t), - privacyConfig: getTransmitUFPDActivityConfig("appnexus", false), - expectedReqNumber: 1, - expectedUserYOB: 0, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "", - expectedSourceTID: "testTID", - }, - { - name: "transmit_precise_geo_allowed", - req: newBidRequest(t), - privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", true), - expectedReqNumber: 1, - expectedUserYOB: 1982, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "testTID", - }, - { - name: "transmit_precise_geo_deny", - req: newBidRequest(t), - privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", false), - expectedReqNumber: 1, - expectedUserYOB: 1982, - expectedUserLat: 123.46, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "testTID", - }, - { - name: "transmit_tid_allowed", - req: newBidRequest(t), - privacyConfig: getTransmitTIDActivityConfig("appnexus", true), - expectedReqNumber: 1, - expectedUserYOB: 1982, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "testTID", - }, - { - name: "transmit_tid_deny", - req: newBidRequest(t), - privacyConfig: getTransmitTIDActivityConfig("appnexus", false), - expectedReqNumber: 1, - expectedUserYOB: 1982, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "", + name: "fetch_bids_request_with_one_bidder_allowed", + req: newBidRequest(t), + privacyConfig: getFetchBidsActivityConfig("appnexus", true), + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + name: "fetch_bids_request_with_one_bidder_not_allowed", + req: newBidRequest(t), + privacyConfig: getFetchBidsActivityConfig("appnexus", false), + expectedReqNumber: 0, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + name: "transmit_ufpd_allowed", + req: newBidRequest(t), + privacyConfig: getTransmitUFPDActivityConfig("appnexus", true), + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + //remove user.eids, user.ext.data.*, user.data.*, user.{id, buyeruid, yob, gender} + //and device-specific IDs + name: "transmit_ufpd_deny", + req: newBidRequest(t), + privacyConfig: getTransmitUFPDActivityConfig("appnexus", false), + expectedReqNumber: 1, + expectedUser: openrtb2.User{ + ID: "", + BuyerUID: "", + Yob: 0, + Geo: &openrtb2.Geo{Lat: 123.456, Lon: 11.278}, + EIDs: nil, + Ext: json.RawMessage(`{"test":2}`), + Data: nil, + }, + expectedDevice: openrtb2.Device{ + UA: deviceUA, + Language: "EN", + IP: "132.173.230.74", + DIDMD5: "", + IFA: "", + DIDSHA1: "", + DPIDMD5: "", + DPIDSHA1: "", + MACMD5: "", + MACSHA1: "", + Geo: &openrtb2.Geo{Lat: 123.456, Lon: 11.278}, + }, + expectedSource: expectedSourceDefault, + }, + { + name: "transmit_precise_geo_allowed", + req: newBidRequest(t), + privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", true), + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + //round user's geographic location by rounding off IP address and lat/lng data. + //this applies to both device.geo and user.geo + name: "transmit_precise_geo_deny", + req: newBidRequest(t), + privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", false), + expectedReqNumber: 1, + expectedUser: openrtb2.User{ + ID: "our-id", + BuyerUID: "their-id", + Yob: 1982, + Geo: &openrtb2.Geo{Lat: 123.46, Lon: 11.28}, + Gender: "test", + Ext: json.RawMessage(`{"data": 1, "test": 2}`), + EIDs: []openrtb2.EID{ + {Source: "eids-source"}, + }, + Data: []openrtb2.Data{{ID: "data-id"}}, + }, + expectedDevice: openrtb2.Device{ + UA: deviceUA, + IP: "132.173.0.0", + Language: "EN", + DIDMD5: "DIDMD5", + IFA: "IFA", + DIDSHA1: "DIDSHA1", + DPIDMD5: "DPIDMD5", + DPIDSHA1: "DPIDSHA1", + MACMD5: "MACMD5", + MACSHA1: "MACSHA1", + Geo: &openrtb2.Geo{Lat: 123.46, Lon: 11.28}, + }, + expectedSource: expectedSourceDefault, + }, + { + name: "transmit_tid_allowed", + req: newBidRequest(t), + privacyConfig: getTransmitTIDActivityConfig("appnexus", true), + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + //remove source.tid and imp.ext.tid + name: "transmit_tid_deny", + req: newBidRequest(t), + privacyConfig: getTransmitTIDActivityConfig("appnexus", false), + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: openrtb2.Source{ + TID: "", + }, + expectedImpExt: json.RawMessage(`{"bidder": {"placementId": 1}}`), }, } for _, test := range testCases { - activities := privacy.NewActivityControl(&test.privacyConfig) - auctionReq := AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: test.req}, - UserSyncs: &emptyUsersync{}, - Activities: activities, - } + t.Run(test.name, func(t *testing.T) { + activities := privacy.NewActivityControl(&test.privacyConfig) + auctionReq := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: test.req}, + UserSyncs: &emptyUsersync{}, + Activities: activities, + Account: config.Account{Privacy: config.AccountPrivacy{ + IPv6Config: config.IPv6{ + AnonKeepBits: 32, + }, + IPv4Config: config.IPv4{ + AnonKeepBits: 16, + }, + }}, + } - bidderToSyncerKey := map[string]string{} - reqSplitter := &requestSplitter{ - bidderToSyncerKey: bidderToSyncerKey, - me: &metrics.MetricsEngineMock{}, - hostSChainNode: nil, - bidderInfo: config.BidderInfos{}, - } + bidderToSyncerKey := map[string]string{} + reqSplitter := &requestSplitter{ + bidderToSyncerKey: bidderToSyncerKey, + me: &metrics.MetricsEngineMock{}, + hostSChainNode: nil, + bidderInfo: config.BidderInfos{}, + } - t.Run(test.name, func(t *testing.T) { bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) assert.Empty(t, errs) assert.Len(t, bidderRequests, test.expectedReqNumber) if test.expectedReqNumber == 1 { - assert.Equal(t, test.expectedUserYOB, bidderRequests[0].BidRequest.User.Yob) - assert.Equal(t, test.expectedUserLat, bidderRequests[0].BidRequest.User.Geo.Lat) - assert.Equal(t, test.expectedDeviceDIDMD5, bidderRequests[0].BidRequest.Device.DIDMD5) - assert.Equal(t, test.expectedSourceTID, bidderRequests[0].BidRequest.Source.TID) + assert.Equal(t, &test.expectedUser, bidderRequests[0].BidRequest.User) + assert.Equal(t, &test.expectedDevice, bidderRequests[0].BidRequest.Device) + assert.Equal(t, &test.expectedSource, bidderRequests[0].BidRequest.Source) + + if len(test.expectedImpExt) > 0 { + assert.JSONEq(t, string(test.expectedImpExt), string(bidderRequests[0].BidRequest.Imp[0].Ext)) + } } }) } diff --git a/ortb/clone.go b/ortb/clone.go index 0dd210a986e..c831aae21b5 100644 --- a/ortb/clone.go +++ b/ortb/clone.go @@ -189,6 +189,140 @@ func CloneUser(s *openrtb2.User) *openrtb2.User { return &c } +func CloneDevice(s *openrtb2.Device) *openrtb2.Device { + if s == nil { + return nil + } + + // Shallow Copy (Value Fields) + c := *s + + // Deep Copy (Pointers) + c.Geo = CloneGeo(s.Geo) + + c.DNT = CloneInt8Pointer(s.DNT) + c.Lmt = CloneInt8Pointer(s.Lmt) + + c.SUA = CloneUserAgent(s.SUA) + if s.ConnectionType != nil { + connectionTypeCopy := s.ConnectionType.Val() + c.ConnectionType = &connectionTypeCopy + } + + c.Ext = sliceutil.Clone(s.Ext) + + return &c +} + +func CloneInt8Pointer(s *int8) *int8 { + if s == nil { + return nil + } + var dntCopy int8 + dntCopy = *s + return &dntCopy +} + +func CloneUserAgent(s *openrtb2.UserAgent) *openrtb2.UserAgent { + if s == nil { + return nil + } + + // Shallow Copy (Value Fields) + c := *s + + // Deep Copy (Pointers) + c.Browsers = CloneBrandVersionSlice(s.Browsers) + c.Platform = CloneBrandVersion(s.Platform) + + if s.Mobile != nil { + mobileCopy := *s.Mobile + c.Mobile = &mobileCopy + } + s.Ext = sliceutil.Clone(s.Ext) + + return &c +} + +func CloneBrandVersionSlice(s []openrtb2.BrandVersion) []openrtb2.BrandVersion { + if s == nil { + return nil + } + + c := make([]openrtb2.BrandVersion, len(s)) + for i, d := range s { + bv := CloneBrandVersion(&d) + c[i] = *bv + } + + return c +} + +func CloneBrandVersion(s *openrtb2.BrandVersion) *openrtb2.BrandVersion { + if s == nil { + return nil + } + c := *s + + // Deep Copy (Pointers) + c.Version = sliceutil.Clone(s.Version) + c.Ext = sliceutil.Clone(s.Ext) + + return &c +} + +func CloneSource(s *openrtb2.Source) *openrtb2.Source { + if s == nil { + return nil + } + + // Shallow Copy (Value Fields) + c := *s + + // Deep Copy (Pointers) + c.SChain = CloneSChain(s.SChain) + c.Ext = sliceutil.Clone(s.Ext) + + return &c +} + +func CloneSChain(s *openrtb2.SupplyChain) *openrtb2.SupplyChain { + if s == nil { + return nil + } + // Shallow Copy (Value Fields) + c := *s + + // Deep Copy (Pointers) + c.Nodes = CloneSupplyChainNodes(s.Nodes) + c.Ext = sliceutil.Clone(s.Ext) + + return &c +} + +func CloneSupplyChainNodes(s []openrtb2.SupplyChainNode) []openrtb2.SupplyChainNode { + if s == nil { + return nil + } + + c := make([]openrtb2.SupplyChainNode, len(s)) + for i, d := range s { + c[i] = CloneSupplyChainNode(d) + } + + return c +} + +func CloneSupplyChainNode(s openrtb2.SupplyChainNode) openrtb2.SupplyChainNode { + // Shallow Copy (Value Fields) Occurred By Passing Argument By Value + + // Deep Copy (Pointers) + s.HP = CloneInt8Pointer(s.HP) + s.Ext = sliceutil.Clone(s.Ext) + + return s +} + func CloneGeo(s *openrtb2.Geo) *openrtb2.Geo { if s == nil { return nil diff --git a/ortb/clone_test.go b/ortb/clone_test.go index 50dd94057ee..820c24397f4 100644 --- a/ortb/clone_test.go +++ b/ortb/clone_test.go @@ -536,6 +536,384 @@ func TestCloneUser(t *testing.T) { }) } +func TestCloneDevice(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneDevice(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.Device{} + result := CloneDevice(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + var n int8 = 1 + np := &n + ct := adcom1.ConnectionWIFI + + given := &openrtb2.Device{ + Geo: &openrtb2.Geo{Lat: 1.2, Lon: 2.3, Ext: json.RawMessage(`{"geo":1}`)}, + DNT: np, + Lmt: np, + UA: "UserAgent", + SUA: &openrtb2.UserAgent{Mobile: np, Model: "iPad"}, + IP: "127.0.0.1", + IPv6: "2001::", + DeviceType: adcom1.DeviceTablet, + Make: "Apple", + Model: "iPad", + OS: "macOS", + OSV: "1.2.3", + HWV: "mini", + H: 20, + W: 30, + PPI: 100, + PxRatio: 200, + JS: 2, + GeoFetch: 4, + FlashVer: "1.22.33", + Language: "En", + LangB: "ENG", + Carrier: "AT&T", + MCCMNC: "111-222", + ConnectionType: &ct, + IFA: "IFA", + DIDSHA1: "DIDSHA1", + DIDMD5: "DIDMD5", + DPIDSHA1: "DPIDSHA1", + DPIDMD5: "DPIDMD5", + MACSHA1: "MACSHA1", + MACMD5: "MACMD5", + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneDevice(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.Geo, result.Geo, "geo") + assert.NotSame(t, given.Geo.Ext, result.Geo.Ext, "geo-ext") + assert.NotSame(t, given.DNT, result.DNT, "dnt") + assert.NotSame(t, given.Lmt, result.Lmt, "lmt") + assert.NotSame(t, given.SUA, result.SUA, "sua") + assert.NotSame(t, given.ConnectionType, result.ConnectionType, "connectionType") + assert.NotSame(t, given.Ext, result.Ext, "ext") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Device{})), + []string{ + "Geo", + "DNT", + "Lmt", + "SUA", + "ConnectionType", + "Ext", + }) + }) +} + +func TestCloneInt8Pointer(t *testing.T) { + + t.Run("nil", func(t *testing.T) { + result := CloneInt8Pointer(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + var given *int8 + result := CloneInt8Pointer(given) + assert.Nil(t, result) + }) + + t.Run("populated", func(t *testing.T) { + var n int8 = 1 + given := &n + result := CloneInt8Pointer(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + }) +} + +func TestCloneUserAgent(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneUserAgent(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.UserAgent{} + result := CloneUserAgent(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + var n int8 = 1 + np := &n + + given := &openrtb2.UserAgent{ + Browsers: []openrtb2.BrandVersion{{Brand: "Apple"}}, + Platform: &openrtb2.BrandVersion{Brand: "Apple"}, + Mobile: np, + Architecture: "X86", + Bitness: "64", + Model: "iPad", + Source: adcom1.UASourceLowEntropy, + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneUserAgent(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.Browsers, result.Browsers, "browsers") + assert.NotSame(t, given.Platform, result.Platform, "platform") + assert.NotSame(t, given.Mobile, result.Mobile, "mobile") + assert.NotSame(t, given.Architecture, result.Architecture, "architecture") + assert.NotSame(t, given.Ext, result.Ext, "ext") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.UserAgent{})), + []string{ + "Browsers", + "Platform", + "Mobile", + "Ext", + }) + }) +} + +func TestCloneBrandVersionSlice(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneBrandVersionSlice(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := []openrtb2.BrandVersion{} + result := CloneBrandVersionSlice(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("one", func(t *testing.T) { + given := []openrtb2.BrandVersion{ + {Brand: "1", Version: []string{"s1", "s2"}, Ext: json.RawMessage(`{"anyField":1}`)}, + } + result := CloneBrandVersionSlice(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item-pointer") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item-pointer-ext") + }) + + t.Run("many", func(t *testing.T) { + given := []openrtb2.BrandVersion{ + {Brand: "1", Version: []string{"s1", "s2"}, Ext: json.RawMessage(`{"anyField":1}`)}, + {Brand: "2", Version: []string{"s3", "s4"}, Ext: json.RawMessage(`{"anyField":1}`)}, + {Brand: "3", Version: []string{"s5", "s6"}, Ext: json.RawMessage(`{"anyField":1}`)}, + } + result := CloneBrandVersionSlice(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item0-pointer") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item0-pointer-ext") + assert.NotSame(t, given[1], result[1], "item1-pointer") + assert.NotSame(t, given[1].Ext, result[1].Ext, "item1-pointer-ext") + assert.NotSame(t, given[2], result[2], "item1-pointer") + assert.NotSame(t, given[2].Ext, result[2].Ext, "item1-pointer-ext") + }) +} + +func TestCloneBrandVersion(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneBrandVersion(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.BrandVersion{} + result := CloneBrandVersion(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + given := &openrtb2.BrandVersion{ + Brand: "Apple", + Version: []string{"s1"}, + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneBrandVersion(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.Ext, result.Ext, "ext") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.BrandVersion{})), + []string{ + "Version", + "Ext", + }) + }) +} + +func TestCloneSource(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneSource(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.Source{} + result := CloneSource(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + + given := &openrtb2.Source{ + FD: 1, + TID: "Tid", + PChain: "PChain", + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Nodes: []openrtb2.SupplyChainNode{ + {ASI: "asi", Ext: json.RawMessage(`{"anyField":1}`)}, + }, + Ext: json.RawMessage(`{"anyField":2}`), + }, + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneSource(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.SChain, result.SChain, "schain") + assert.NotSame(t, given.SChain.Ext, result.SChain.Ext, "schain.ext") + assert.NotSame(t, given.Ext, result.Ext, "ext") + assert.NotSame(t, given.SChain.Nodes[0].Ext, result.SChain.Nodes[0].Ext, "schain.nodes.ext") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Source{})), + []string{ + "SChain", + "Ext", + }) + }) +} + +func TestCloneSChain(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneSource(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.SupplyChain{} + result := CloneSChain(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + given := &openrtb2.SupplyChain{ + Complete: 1, + Nodes: []openrtb2.SupplyChainNode{ + {ASI: "asi", Ext: json.RawMessage(`{"anyField":1}`)}, + }, + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneSChain(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.Nodes, result.Nodes, "nodes") + assert.NotSame(t, given.Nodes[0].Ext, result.Nodes[0].Ext, "nodes.ext") + assert.NotSame(t, given.Ext, result.Ext, "ext") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.SupplyChain{})), + []string{ + "Nodes", + "Ext", + }) + }) +} + +func TestCloneSupplyChainNodes(t *testing.T) { + var n int8 = 1 + np := &n + t.Run("nil", func(t *testing.T) { + result := CloneSupplyChainNodes(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := []openrtb2.SupplyChainNode{} + result := CloneSupplyChainNodes(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("one", func(t *testing.T) { + given := []openrtb2.SupplyChainNode{ + {ASI: "asi", HP: np, Ext: json.RawMessage(`{"anyField":1}`)}, + } + result := CloneSupplyChainNodes(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item-pointer") + assert.NotSame(t, given[0].HP, result[0].HP, "item-pointer-hp") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item-pointer-ext") + }) + + t.Run("many", func(t *testing.T) { + given := []openrtb2.SupplyChainNode{ + {ASI: "asi", HP: np, Ext: json.RawMessage(`{"anyField":1}`)}, + {ASI: "asi", HP: np, Ext: json.RawMessage(`{"anyField":1}`)}, + {ASI: "asi", HP: np, Ext: json.RawMessage(`{"anyField":1}`)}, + } + result := CloneSupplyChainNodes(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item0-pointer") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item0-pointer-ext") + assert.NotSame(t, given[0].HP, result[0].HP, "item0-pointer-hp") + assert.NotSame(t, given[1], result[1], "item1-pointer") + assert.NotSame(t, given[1].Ext, result[1].Ext, "item1-pointer-ext") + assert.NotSame(t, given[1].HP, result[1].HP, "item1-pointer-hp") + assert.NotSame(t, given[2], result[2], "item2-pointer") + assert.NotSame(t, given[2].Ext, result[2].Ext, "item2-pointer-ext") + assert.NotSame(t, given[2].HP, result[2].HP, "item2-pointer-hp") + }) +} + +func TestCloneSupplyChainNode(t *testing.T) { + t.Run("populated", func(t *testing.T) { + var n int8 = 1 + np := &n + + given := openrtb2.SupplyChainNode{ + ASI: "asi", + HP: np, + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneSupplyChainNode(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.Ext, result.Ext, "ext") + assert.NotSame(t, given.HP, result.HP, "hp") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.SupplyChainNode{})), + []string{ + "HP", + "Ext", + }) + }) +} + func TestCloneGeo(t *testing.T) { t.Run("nil", func(t *testing.T) { result := CloneGeo(nil) diff --git a/privacy/enforcement.go b/privacy/enforcement.go deleted file mode 100644 index 901c062ea4e..00000000000 --- a/privacy/enforcement.go +++ /dev/null @@ -1,92 +0,0 @@ -package privacy - -import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/v2/config" -) - -// Enforcement represents the privacy policies to enforce for an OpenRTB bid request. -type Enforcement struct { - CCPA bool - COPPA bool - GDPRGeo bool - GDPRID bool - LMT bool - - // activities - UFPD bool - Eids bool - PreciseGeo bool - TID bool -} - -// Any returns true if at least one privacy policy requires enforcement. -func (e Enforcement) AnyLegacy() bool { - return e.CCPA || e.COPPA || e.GDPRGeo || e.GDPRID || e.LMT -} - -func (e Enforcement) AnyActivities() bool { - return e.UFPD || e.PreciseGeo || e.Eids || e.TID -} - -// Apply cleans personally identifiable information from an OpenRTB bid request. -func (e Enforcement) Apply(bidRequest *openrtb2.BidRequest, privacy config.AccountPrivacy) { - e.apply(bidRequest, NewScrubber(privacy.IPv6Config, privacy.IPv4Config)) -} - -func (e Enforcement) apply(bidRequest *openrtb2.BidRequest, scrubber Scrubber) { - if bidRequest != nil { - if e.AnyActivities() { - bidRequest = scrubber.ScrubRequest(bidRequest, e) - } - if e.AnyLegacy() && !(e.UFPD && e.PreciseGeo && e.Eids) { - bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(), e.getGeoScrubStrategy()) - } - if e.AnyLegacy() && !(e.UFPD && e.PreciseGeo) { - bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getDeviceIDScrubStrategy(), e.getIPv4ScrubStrategy(), e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy()) - } - } -} - -func (e Enforcement) getDeviceIDScrubStrategy() ScrubStrategyDeviceID { - if e.COPPA || e.GDPRID || e.CCPA || e.LMT { - return ScrubStrategyDeviceIDAll - } - - return ScrubStrategyDeviceIDNone -} - -func (e Enforcement) getIPv4ScrubStrategy() ScrubStrategyIPV4 { - if e.COPPA || e.GDPRGeo || e.CCPA || e.LMT { - return ScrubStrategyIPV4Subnet - } - - return ScrubStrategyIPV4None -} - -func (e Enforcement) getIPv6ScrubStrategy() ScrubStrategyIPV6 { - if e.GDPRGeo || e.CCPA || e.LMT || e.COPPA { - return ScrubStrategyIPV6Subnet - } - return ScrubStrategyIPV6None -} - -func (e Enforcement) getGeoScrubStrategy() ScrubStrategyGeo { - if e.COPPA { - return ScrubStrategyGeoFull - } - - if e.GDPRGeo || e.CCPA || e.LMT { - return ScrubStrategyGeoReducedPrecision - } - - return ScrubStrategyGeoNone -} - -func (e Enforcement) getUserScrubStrategy() ScrubStrategyUser { - if e.COPPA || e.CCPA || e.LMT || e.GDPRID { - return ScrubStrategyUserIDAndDemographic - } - - return ScrubStrategyUserNone -} diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go deleted file mode 100644 index a97779eb903..00000000000 --- a/privacy/enforcement_test.go +++ /dev/null @@ -1,393 +0,0 @@ -package privacy - -import ( - "testing" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestAnyLegacy(t *testing.T) { - testCases := []struct { - enforcement Enforcement - expected bool - description string - }{ - { - description: "All False", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: false, - GDPRID: false, - LMT: false, - }, - expected: false, - }, - { - description: "All True", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - }, - expected: true, - }, - { - description: "Mixed", - enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPRGeo: false, - GDPRID: false, - LMT: true, - }, - expected: true, - }, - } - - for _, test := range testCases { - result := test.enforcement.AnyLegacy() - assert.Equal(t, test.expected, result, test.description) - } -} - -func TestApplyGDPR(t *testing.T) { - testCases := []struct { - description string - enforcement Enforcement - expectedDeviceID ScrubStrategyDeviceID - expectedDeviceIPv4 ScrubStrategyIPV4 - expectedDeviceIPv6 ScrubStrategyIPV6 - expectedDeviceGeo ScrubStrategyGeo - expectedUser ScrubStrategyUser - expectedUserGeo ScrubStrategyGeo - }{ - { - description: "All Enforced", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoFull, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoFull, - }, - { - description: "CCPA Only", - enforcement: Enforcement{ - CCPA: true, - COPPA: false, - GDPRGeo: false, - GDPRID: false, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "COPPA Only", - enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPRGeo: false, - GDPRID: false, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoFull, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoFull, - }, - { - description: "GDPR Only - Full", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: true, - GDPRID: true, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "GDPR Only - ID Only", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: false, - GDPRID: true, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4None, - expectedDeviceIPv6: ScrubStrategyIPV6None, - expectedDeviceGeo: ScrubStrategyGeoNone, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoNone, - }, - { - description: "GDPR Only - Geo Only", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: true, - GDPRID: false, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDNone, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserNone, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "LMT Only", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: false, - GDPRID: false, - LMT: true, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "Interactions: COPPA + GDPR Full", - enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoFull, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoFull, - }, - } - - for _, test := range testCases { - req := &openrtb2.BidRequest{ - Device: &openrtb2.Device{}, - User: &openrtb2.User{}, - } - replacedDevice := &openrtb2.Device{} - replacedUser := &openrtb2.User{} - - m := &mockScrubber{} - m.On("ScrubDevice", req.Device, test.expectedDeviceID, test.expectedDeviceIPv4, test.expectedDeviceIPv6, test.expectedDeviceGeo).Return(replacedDevice).Once() - m.On("ScrubUser", req.User, test.expectedUser, test.expectedUserGeo).Return(replacedUser).Once() - - test.enforcement.apply(req, m) - - m.AssertExpectations(t) - assert.Same(t, replacedDevice, req.Device, "Device") - assert.Same(t, replacedUser, req.User, "User") - } -} - -func TestApplyToggle(t *testing.T) { - testCases := []struct { - description string - enforcement Enforcement - expectedScrubRequestExecuted bool - expectedScrubUserExecuted bool - expectedScrubDeviceExecuted bool - }{ - { - description: "All enforced - only ScrubRequest execution expected", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - UFPD: true, - Eids: true, - PreciseGeo: true, - TID: true, - }, - expectedScrubRequestExecuted: true, - expectedScrubUserExecuted: false, - expectedScrubDeviceExecuted: false, - }, - { - description: "All Legacy and no activities - ScrubUser and ScrubDevice execution expected", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - UFPD: false, - Eids: false, - PreciseGeo: false, - TID: false, - }, - expectedScrubRequestExecuted: false, - expectedScrubUserExecuted: true, - expectedScrubDeviceExecuted: true, - }, - { - description: "Some Legacy and some activities - ScrubRequest, ScrubUser and ScrubDevice execution expected", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - UFPD: true, - Eids: false, - PreciseGeo: false, - TID: false, - }, - expectedScrubRequestExecuted: true, - expectedScrubUserExecuted: true, - expectedScrubDeviceExecuted: true, - }, - { - description: "Some Legacy and some activities - ScrubRequest execution expected", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - UFPD: true, - Eids: true, - PreciseGeo: true, - TID: false, - }, - expectedScrubRequestExecuted: true, - expectedScrubUserExecuted: false, - expectedScrubDeviceExecuted: false, - }, - { - description: "Some Legacy and some activities overlap - ScrubRequest and ScrubUser execution expected", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - UFPD: true, - Eids: false, - PreciseGeo: true, - TID: false, - }, - expectedScrubRequestExecuted: true, - expectedScrubUserExecuted: true, - expectedScrubDeviceExecuted: false, - }, - } - - for _, test := range testCases { - t.Run(test.description, func(t *testing.T) { - req := &openrtb2.BidRequest{ - Device: &openrtb2.Device{}, - User: &openrtb2.User{}, - } - replacedDevice := &openrtb2.Device{} - replacedUser := &openrtb2.User{} - - m := &mockScrubber{} - - if test.expectedScrubRequestExecuted { - m.On("ScrubRequest", req, test.enforcement).Return(req).Once() - } - if test.expectedScrubUserExecuted { - m.On("ScrubUser", req.User, ScrubStrategyUserIDAndDemographic, ScrubStrategyGeoFull).Return(replacedUser).Once() - } - if test.expectedScrubDeviceExecuted { - m.On("ScrubDevice", req.Device, ScrubStrategyDeviceIDAll, ScrubStrategyIPV4Subnet, ScrubStrategyIPV6Subnet, ScrubStrategyGeoFull).Return(replacedDevice).Once() - } - - test.enforcement.apply(req, m) - - m.AssertExpectations(t) - - }) - } -} - -func TestApplyNoneApplicable(t *testing.T) { - req := &openrtb2.BidRequest{} - - m := &mockScrubber{} - - enforcement := Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: false, - GDPRID: false, - LMT: false, - - UFPD: false, - PreciseGeo: false, - TID: false, - Eids: false, - } - enforcement.apply(req, m) - - m.AssertNotCalled(t, "ScrubDevice") - m.AssertNotCalled(t, "ScrubUser") -} - -func TestApplyNil(t *testing.T) { - m := &mockScrubber{} - - enforcement := Enforcement{} - enforcement.apply(nil, m) - - m.AssertNotCalled(t, "ScrubDevice") - m.AssertNotCalled(t, "ScrubUser") -} - -type mockScrubber struct { - mock.Mock -} - -func (m *mockScrubber) ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enforcement) *openrtb2.BidRequest { - args := m.Called(bidRequest, enforcement) - return args.Get(0).(*openrtb2.BidRequest) -} - -func (m *mockScrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device { - args := m.Called(device, id, ipv4, ipv6, geo) - return args.Get(0).(*openrtb2.Device) -} - -func (m *mockScrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User { - args := m.Called(user, strategy, geo) - return args.Get(0).(*openrtb2.User) -} diff --git a/privacy/scrubber.go b/privacy/scrubber.go index 54941669ab9..ba1f8a9478b 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -2,252 +2,147 @@ package privacy import ( "encoding/json" + "github.com/prebid/prebid-server/v2/util/jsonutil" "net" + "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/util/iputil" - "github.com/prebid/prebid-server/v2/util/jsonutil" - "github.com/prebid/prebid-server/v2/util/ptrutil" - - "github.com/prebid/openrtb/v19/openrtb2" -) - -// ScrubStrategyIPV4 defines the approach to scrub PII from an IPV4 address. -type ScrubStrategyIPV4 int - -const ( - // ScrubStrategyIPV4None does not remove any part of an IPV4 address. - ScrubStrategyIPV4None ScrubStrategyIPV4 = iota - - // ScrubStrategyIPV4Subnet zeroes out the last 8 bits of an IPV4 address. - ScrubStrategyIPV4Subnet -) - -// ScrubStrategyIPV6 defines the approach to scrub PII from an IPV6 address. -type ScrubStrategyIPV6 int - -const ( - // ScrubStrategyIPV6None does not remove any part of an IPV6 address. - ScrubStrategyIPV6None ScrubStrategyIPV6 = iota - - // ScrubStrategyIPV6Subnet zeroes out the last 16 bits of an IPV6 sub net address. - ScrubStrategyIPV6Subnet -) - -// ScrubStrategyGeo defines the approach to scrub PII from geographical data. -type ScrubStrategyGeo int - -const ( - // ScrubStrategyGeoNone does not remove any geographical data. - ScrubStrategyGeoNone ScrubStrategyGeo = iota - - // ScrubStrategyGeoFull removes all geographical data. - ScrubStrategyGeoFull - - // ScrubStrategyGeoReducedPrecision anonymizes geographical data with rounding. - ScrubStrategyGeoReducedPrecision -) - -// ScrubStrategyUser defines the approach to scrub PII from user data. -type ScrubStrategyUser int - -const ( - // ScrubStrategyUserNone does not remove non-location data. - ScrubStrategyUserNone ScrubStrategyUser = iota - - // ScrubStrategyUserIDAndDemographic removes the user's buyer id, exchange id year of birth, and gender. - ScrubStrategyUserIDAndDemographic -) - -// ScrubStrategyDeviceID defines the approach to remove hardware id and device id data. -type ScrubStrategyDeviceID int - -const ( - // ScrubStrategyDeviceIDNone does not remove hardware id and device id data. - ScrubStrategyDeviceIDNone ScrubStrategyDeviceID = iota - - // ScrubStrategyDeviceIDAll removes all hardware and device id data (ifa, mac hashes device id hashes) - ScrubStrategyDeviceIDAll ) -// Scrubber removes PII from parts of an OpenRTB request. -type Scrubber interface { - ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enforcement) *openrtb2.BidRequest - ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device - ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User +type IPConf struct { + IPV6 config.IPv6 + IPV4 config.IPv4 } -type scrubber struct { - ipV6 config.IPv6 - ipV4 config.IPv4 +func scrubDeviceIDs(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.Device != nil { + reqWrapper.Device.DIDMD5 = "" + reqWrapper.Device.DIDSHA1 = "" + reqWrapper.Device.DPIDMD5 = "" + reqWrapper.Device.DPIDSHA1 = "" + reqWrapper.Device.IFA = "" + reqWrapper.Device.MACMD5 = "" + reqWrapper.Device.MACSHA1 = "" + } } -// NewScrubber returns an OpenRTB scrubber. -func NewScrubber(ipV6 config.IPv6, ipV4 config.IPv4) Scrubber { - return scrubber{ - ipV6: ipV6, - ipV4: ipV4, +func scrubUserIDs(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.User != nil { + reqWrapper.User.Data = nil + reqWrapper.User.ID = "" + reqWrapper.User.BuyerUID = "" + reqWrapper.User.Yob = 0 + reqWrapper.User.Gender = "" + reqWrapper.User.Keywords = "" + reqWrapper.User.KwArray = nil } } -func (s scrubber) ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enforcement) *openrtb2.BidRequest { - var userExtParsed map[string]json.RawMessage - userExtModified := false - - // expressed in two lines because IntelliJ cannot infer the generic type - var userCopy *openrtb2.User - userCopy = ptrutil.Clone(bidRequest.User) - - // expressed in two lines because IntelliJ cannot infer the generic type - var deviceCopy *openrtb2.Device - deviceCopy = ptrutil.Clone(bidRequest.Device) - - if userCopy != nil && (enforcement.UFPD || enforcement.Eids) { - if len(userCopy.Ext) != 0 { - jsonutil.Unmarshal(userCopy.Ext, &userExtParsed) - } +func scrubUserDemographics(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.User != nil { + reqWrapper.User.BuyerUID = "" + reqWrapper.User.ID = "" + reqWrapper.User.Yob = 0 + reqWrapper.User.Gender = "" } +} - if enforcement.UFPD { - // transmitUfpd covers user.ext.data, user.data, user.id, user.buyeruid, user.yob, user.gender, user.keywords, user.kwarray - // and device.{ifa, macsha1, macmd5, dpidsha1, dpidmd5, didsha1, didmd5} - if deviceCopy != nil { - deviceCopy.DIDMD5 = "" - deviceCopy.DIDSHA1 = "" - deviceCopy.DPIDMD5 = "" - deviceCopy.DPIDSHA1 = "" - deviceCopy.IFA = "" - deviceCopy.MACMD5 = "" - deviceCopy.MACSHA1 = "" - } - if userCopy != nil { - userCopy.Data = nil - userCopy.ID = "" - userCopy.BuyerUID = "" - userCopy.Yob = 0 - userCopy.Gender = "" - userCopy.Keywords = "" - userCopy.KwArray = nil - - _, hasField := userExtParsed["data"] - if hasField { - delete(userExtParsed, "data") - userExtModified = true - } +func scrubUserExt(reqWrapper *openrtb_ext.RequestWrapper, fieldName string) error { + if reqWrapper.User != nil { + userExt, err := reqWrapper.GetUserExt() + if err != nil { + return err } - } - if enforcement.Eids { - //transmitEids covers user.eids and user.ext.eids - if userCopy != nil { - userCopy.EIDs = nil - _, hasField := userExtParsed["eids"] - if hasField { - delete(userExtParsed, "eids") - userExtModified = true - } + ext := userExt.GetExt() + _, hasField := ext[fieldName] + if hasField { + delete(ext, fieldName) + userExt.SetExt(ext) } } + return nil +} - if userExtModified { - userExt, _ := jsonutil.Marshal(userExtParsed) - userCopy.Ext = userExt +func ScrubEIDs(reqWrapper *openrtb_ext.RequestWrapper) error { + //transmitEids removes user.eids and user.ext.eids + if reqWrapper.User != nil { + reqWrapper.User.EIDs = nil } + return scrubUserExt(reqWrapper, "eids") +} - if enforcement.TID { - //remove source.tid and imp.ext.tid - if bidRequest.Source != nil { - sourceCopy := ptrutil.Clone(bidRequest.Source) - sourceCopy.TID = "" - bidRequest.Source = sourceCopy - } - for ind, imp := range bidRequest.Imp { - impExt := scrubExtIDs(imp.Ext, "tid") - bidRequest.Imp[ind].Ext = impExt - } +func ScrubTID(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.Source != nil { + reqWrapper.Source.TID = "" } - - if enforcement.PreciseGeo { - //round user's geographic location by rounding off IP address and lat/lng data. - //this applies to both device.geo and user.geo - if userCopy != nil && userCopy.Geo != nil { - userCopy.Geo = scrubGeoPrecision(userCopy.Geo) - } - - if deviceCopy != nil { - if deviceCopy.Geo != nil { - deviceCopy.Geo = scrubGeoPrecision(deviceCopy.Geo) - } - deviceCopy.IP = scrubIP(deviceCopy.IP, s.ipV4.AnonKeepBits, iputil.IPv4BitSize) - deviceCopy.IPv6 = scrubIP(deviceCopy.IPv6, s.ipV6.AnonKeepBits, iputil.IPv6BitSize) - } + impWrapper := reqWrapper.GetImp() + for ind, imp := range impWrapper { + impExt := scrubExtIDs(imp.Ext, "tid") + impWrapper[ind].Ext = impExt } - - bidRequest.Device = deviceCopy - bidRequest.User = userCopy - return bidRequest + reqWrapper.SetImp(impWrapper) } -func (s scrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device { - if device == nil { - return nil - } - - deviceCopy := *device - - switch id { - case ScrubStrategyDeviceIDAll: - deviceCopy.DIDMD5 = "" - deviceCopy.DIDSHA1 = "" - deviceCopy.DPIDMD5 = "" - deviceCopy.DPIDSHA1 = "" - deviceCopy.IFA = "" - deviceCopy.MACMD5 = "" - deviceCopy.MACSHA1 = "" +func scrubGEO(reqWrapper *openrtb_ext.RequestWrapper) { + //round user's geographic location by rounding off IP address and lat/lng data. + //this applies to both device.geo and user.geo + if reqWrapper.User != nil && reqWrapper.User.Geo != nil { + reqWrapper.User.Geo = scrubGeoPrecision(reqWrapper.User.Geo) } - switch ipv4 { - case ScrubStrategyIPV4Subnet: - deviceCopy.IP = scrubIP(device.IP, s.ipV4.AnonKeepBits, iputil.IPv4BitSize) + if reqWrapper.Device != nil && reqWrapper.Device.Geo != nil { + reqWrapper.Device.Geo = scrubGeoPrecision(reqWrapper.Device.Geo) } +} - switch ipv6 { - case ScrubStrategyIPV6Subnet: - deviceCopy.IPv6 = scrubIP(device.IPv6, s.ipV6.AnonKeepBits, iputil.IPv6BitSize) +func scrubGeoFull(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.User != nil && reqWrapper.User.Geo != nil { + reqWrapper.User.Geo = &openrtb2.Geo{} } - - switch geo { - case ScrubStrategyGeoFull: - deviceCopy.Geo = scrubGeoFull(device.Geo) - case ScrubStrategyGeoReducedPrecision: - deviceCopy.Geo = scrubGeoPrecision(device.Geo) + if reqWrapper.Device != nil && reqWrapper.Device.Geo != nil { + reqWrapper.Device.Geo = &openrtb2.Geo{} } - return &deviceCopy } -func (scrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User { - if user == nil { - return nil +func scrubDeviceIP(reqWrapper *openrtb_ext.RequestWrapper, ipConf IPConf) { + if reqWrapper.Device != nil { + reqWrapper.Device.IP = scrubIP(reqWrapper.Device.IP, ipConf.IPV4.AnonKeepBits, iputil.IPv4BitSize) + reqWrapper.Device.IPv6 = scrubIP(reqWrapper.Device.IPv6, ipConf.IPV6.AnonKeepBits, iputil.IPv6BitSize) } +} - userCopy := *user +func ScrubDeviceIDsIPsUserDemoExt(reqWrapper *openrtb_ext.RequestWrapper, ipConf IPConf, fieldName string, scrubFullGeo bool) { + scrubDeviceIDs(reqWrapper) + scrubDeviceIP(reqWrapper, ipConf) + scrubUserDemographics(reqWrapper) + scrubUserExt(reqWrapper, fieldName) - if strategy == ScrubStrategyUserIDAndDemographic { - userCopy.BuyerUID = "" - userCopy.ID = "" - userCopy.Ext = scrubExtIDs(userCopy.Ext, "eids") - userCopy.Yob = 0 - userCopy.Gender = "" + if scrubFullGeo { + scrubGeoFull(reqWrapper) + } else { + scrubGEO(reqWrapper) } +} - switch geo { - case ScrubStrategyGeoFull: - userCopy.Geo = scrubGeoFull(user.Geo) - case ScrubStrategyGeoReducedPrecision: - userCopy.Geo = scrubGeoPrecision(user.Geo) - } +func ScrubUserFPD(reqWrapper *openrtb_ext.RequestWrapper) { + scrubDeviceIDs(reqWrapper) + scrubUserIDs(reqWrapper) + scrubUserExt(reqWrapper, "data") + reqWrapper.User.EIDs = nil +} - return &userCopy +func ScrubGdprID(reqWrapper *openrtb_ext.RequestWrapper) { + scrubDeviceIDs(reqWrapper) + scrubUserDemographics(reqWrapper) + scrubUserExt(reqWrapper, "eids") +} + +func ScrubGeoAndDeviceIP(reqWrapper *openrtb_ext.RequestWrapper, ipConf IPConf) { + scrubDeviceIP(reqWrapper, ipConf) + scrubGEO(reqWrapper) } func scrubIP(ip string, ones, bits int) string { @@ -259,14 +154,6 @@ func scrubIP(ip string, ones, bits int) string { return ipMasked.String() } -func scrubGeoFull(geo *openrtb2.Geo) *openrtb2.Geo { - if geo == nil { - return nil - } - - return &openrtb2.Geo{} -} - func scrubGeoPrecision(geo *openrtb2.Geo) *openrtb2.Geo { if geo == nil { return nil diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index 299acd9eba3..1fb88874d43 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -4,453 +4,347 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/v2/config" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) -func TestScrubDevice(t *testing.T) { - device := getTestDevice() - +func TestScrubDeviceIDs(t *testing.T) { testCases := []struct { - description string - expected *openrtb2.Device - id ScrubStrategyDeviceID - ipv4 ScrubStrategyIPV4 - ipv6 ScrubStrategyIPV6 - geo ScrubStrategyGeo + name string + deviceIn *openrtb2.Device + expectedDevice *openrtb2.Device }{ { - description: "All Strategies - None", - expected: device, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoNone, - }, - { - description: "All Strategies - Strictest", - expected: &openrtb2.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", - IP: "1.2.3.0", - IPv6: "2001:1db8:2233:4400::", - Geo: &openrtb2.Geo{}, - }, - id: ScrubStrategyDeviceIDAll, - ipv4: ScrubStrategyIPV4Subnet, - ipv6: ScrubStrategyIPV6Subnet, - geo: ScrubStrategyGeoFull, - }, - { - description: "Isolated - ID - All", - expected: &openrtb2.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: device.Geo, - }, - id: ScrubStrategyDeviceIDAll, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoNone, - }, - { - description: "Isolated - IPv4 - Lowest 8", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.0", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: device.Geo, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4Subnet, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoNone, - }, - { - description: "Isolated - IPv6", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4400::", - Geo: device.Geo, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6Subnet, - geo: ScrubStrategyGeoNone, - }, - { - description: "Isolated - Geo - Reduced Precision", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: &openrtb2.Geo{ - Lat: 123.46, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "Isolated - Geo - Full", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: &openrtb2.Geo{}, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoFull, + name: "all", + deviceIn: &openrtb2.Device{DIDMD5: "MD5", DIDSHA1: "SHA1", DPIDMD5: "MD5", DPIDSHA1: "SHA1", IFA: "IFA", MACMD5: "MD5", MACSHA1: "SHA1"}, + expectedDevice: &openrtb2.Device{DIDMD5: "", DIDSHA1: "", DPIDMD5: "", DPIDSHA1: "", IFA: "", MACMD5: "", MACSHA1: ""}, + }, + { + name: "nil", + deviceIn: nil, + expectedDevice: nil, }, } - testIPMasking := getTestIPMasking() for _, test := range testCases { - result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubDevice(device, test.id, test.ipv4, test.ipv6, test.geo) - assert.Equal(t, test.expected, result, test.description) + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Device: test.deviceIn}} + scrubDeviceIDs(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedDevice, brw.Device) + }) } } -func TestScrubDeviceNil(t *testing.T) { - testIPMasking := getTestIPMasking() - result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubDevice(nil, ScrubStrategyDeviceIDNone, ScrubStrategyIPV4None, ScrubStrategyIPV6None, ScrubStrategyGeoNone) - assert.Nil(t, result) +func TestScrubUserIDs(t *testing.T) { + testCases := []struct { + name string + userIn *openrtb2.User + expectedUser *openrtb2.User + }{ + { + name: "all", + userIn: &openrtb2.User{Data: []openrtb2.Data{}, ID: "ID", BuyerUID: "bID", Yob: 2000, Gender: "M", Keywords: "keywords", KwArray: nil}, + expectedUser: &openrtb2.User{Data: nil, ID: "", BuyerUID: "", Yob: 0, Gender: "", Keywords: "", KwArray: nil}, + }, + { + name: "nil", + userIn: nil, + expectedUser: nil, + }, + } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn}} + scrubUserIDs(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + }) + } } -func TestScrubUser(t *testing.T) { - user := getTestUser() - +func TestScrubUserDemographics(t *testing.T) { testCases := []struct { - description string - expected *openrtb2.User - scrubUser ScrubStrategyUser - scrubGeo ScrubStrategyGeo + name string + userIn *openrtb2.User + expectedUser *openrtb2.User }{ { - description: "User ID And Demographic & Geo Full", - expected: &openrtb2.User{ - ID: "", - BuyerUID: "", - Yob: 0, - Gender: "", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{}, - }, - scrubUser: ScrubStrategyUserIDAndDemographic, - scrubGeo: ScrubStrategyGeoFull, - }, - { - description: "User ID And Demographic & Geo Reduced", - expected: &openrtb2.User{ - ID: "", - BuyerUID: "", - Yob: 0, - Gender: "", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.46, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - scrubUser: ScrubStrategyUserIDAndDemographic, - scrubGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "User ID And Demographic & Geo None", - expected: &openrtb2.User{ - ID: "", - BuyerUID: "", - Yob: 0, - Gender: "", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - scrubUser: ScrubStrategyUserIDAndDemographic, - scrubGeo: ScrubStrategyGeoNone, - }, - { - description: "User None & Geo Full", - expected: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{}, - }, - scrubUser: ScrubStrategyUserNone, - scrubGeo: ScrubStrategyGeoFull, - }, - { - description: "User None & Geo Reduced", - expected: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.46, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - scrubUser: ScrubStrategyUserNone, - scrubGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "User None & Geo None", - expected: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - scrubUser: ScrubStrategyUserNone, - scrubGeo: ScrubStrategyGeoNone, + name: "all", + userIn: &openrtb2.User{ID: "ID", BuyerUID: "bID", Yob: 2000, Gender: "M"}, + expectedUser: &openrtb2.User{ID: "", BuyerUID: "", Yob: 0, Gender: ""}, + }, + { + name: "nil", + userIn: nil, + expectedUser: nil, }, } - - testIPMasking := getTestIPMasking() for _, test := range testCases { - result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubUser(user, test.scrubUser, test.scrubGeo) - assert.Equal(t, test.expected, result, test.description) + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn}} + scrubUserDemographics(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + }) } } -func TestScrubUserNil(t *testing.T) { - testIPMasking := getTestIPMasking() - result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubUser(nil, ScrubStrategyUserNone, ScrubStrategyGeoNone) - assert.Nil(t, result) +func TestScrubUserExt(t *testing.T) { + testCases := []struct { + name string + userIn *openrtb2.User + fieldName string + expectedUser *openrtb2.User + }{ + { + name: "nil_user", + userIn: nil, + expectedUser: nil, + }, + { + name: "nil_ext", + userIn: &openrtb2.User{ID: "ID", Ext: nil}, + expectedUser: &openrtb2.User{ID: "ID", Ext: nil}, + }, + { + name: "empty_ext", + userIn: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{}`)}, + expectedUser: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{}`)}, + }, + { + name: "ext_with_field", + userIn: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{"data":"123","test":1}`)}, + fieldName: "data", + expectedUser: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{"test":1}`)}, + }, + { + name: "ext_without_field", + userIn: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{"data":"123","test":1}`)}, + fieldName: "noData", + expectedUser: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{"data":"123","test":1}`)}, + }, + { + name: "nil", + userIn: nil, + expectedUser: nil, + }, + } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn}} + scrubUserExt(brw, test.fieldName) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + }) + } } -func TestScrubRequest(t *testing.T) { - - imps := []openrtb2.Imp{ - {ID: "testId", Ext: json.RawMessage(`{"test": 1, "tid": 2}`)}, +func TestScrubEids(t *testing.T) { + testCases := []struct { + name string + userIn *openrtb2.User + expectedUser *openrtb2.User + }{ + { + name: "eids", + userIn: &openrtb2.User{ID: "ID", EIDs: []openrtb2.EID{}}, + expectedUser: &openrtb2.User{ID: "ID", EIDs: nil}, + }, + { + name: "nil_eids", + userIn: &openrtb2.User{ID: "ID", EIDs: nil}, + expectedUser: &openrtb2.User{ID: "ID", EIDs: nil}, + }, + { + name: "nil", + userIn: nil, + expectedUser: nil, + }, } - source := &openrtb2.Source{ - TID: "testTid", + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn}} + ScrubEIDs(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + }) } - device := getTestDevice() - user := getTestUser() - user.Ext = json.RawMessage(`{"data": 1, "eids": 2}`) - user.EIDs = []openrtb2.EID{{Source: "test"}} +} +func TestScrubTID(t *testing.T) { testCases := []struct { - description string - enforcement Enforcement - userExtPresent bool - expected *openrtb2.BidRequest + name string + sourceIn *openrtb2.Source + impIn []openrtb2.Imp + expectedSource *openrtb2.Source + expectedImp []openrtb2.Imp }{ { - description: "enforce transmitUFPD with user.ext", - enforcement: Enforcement{UFPD: true}, - userExtPresent: true, - expected: &openrtb2.BidRequest{ - Imp: imps, - Source: source, - User: &openrtb2.User{ - EIDs: []openrtb2.EID{{Source: "test"}}, - Geo: user.Geo, - Ext: json.RawMessage(`{"eids":2}`), - }, - Device: &openrtb2.Device{ - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: device.Geo, - }, - }, - }, - { - description: "enforce transmitUFPD without user.ext", - enforcement: Enforcement{UFPD: true}, - userExtPresent: false, - expected: &openrtb2.BidRequest{ - Imp: imps, - Source: source, - User: &openrtb2.User{ - EIDs: []openrtb2.EID{{Source: "test"}}, - Geo: user.Geo, - }, - Device: &openrtb2.Device{ - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: device.Geo, - }, - }, - }, - { - description: "enforce transmitEids", - enforcement: Enforcement{Eids: true}, - userExtPresent: true, - expected: &openrtb2.BidRequest{ - Imp: imps, - Source: source, - Device: device, - User: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Geo: user.Geo, - EIDs: nil, - Ext: json.RawMessage(`{"data":1}`), - }, - }, - }, - { - description: "enforce transmitTid", - enforcement: Enforcement{TID: true}, - userExtPresent: true, - expected: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{ - {ID: "testId", Ext: json.RawMessage(`{"test":1}`)}, - }, - Source: &openrtb2.Source{ - TID: "", - }, - Device: device, - User: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Geo: user.Geo, - EIDs: []openrtb2.EID{{Source: "test"}}, - Ext: json.RawMessage(`{"data": 1, "eids": 2}`), - }, - }, - }, - { - description: "enforce precise Geo", - enforcement: Enforcement{PreciseGeo: true}, - userExtPresent: true, - expected: &openrtb2.BidRequest{ - Imp: imps, - Source: source, - User: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Geo: &openrtb2.Geo{ - Lat: 123.46, Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - EIDs: []openrtb2.EID{{Source: "test"}}, - Ext: json.RawMessage(`{"data": 1, "eids": 2}`), - }, - Device: &openrtb2.Device{ - IFA: "anyIFA", - DIDSHA1: "anyDIDSHA1", - DIDMD5: "anyDIDMD5", - DPIDSHA1: "anyDPIDSHA1", - DPIDMD5: "anyDPIDMD5", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IP: "1.2.3.0", - IPv6: "2001:1db8:2233:4400::", - Geo: &openrtb2.Geo{ - Lat: 123.46, Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - }, + name: "nil", + sourceIn: nil, + expectedSource: nil, + }, + { + name: "nil_imp_ext", + sourceIn: &openrtb2.Source{TID: "tid"}, + impIn: []openrtb2.Imp{{ID: "impID", Ext: nil}}, + expectedSource: &openrtb2.Source{TID: ""}, + expectedImp: []openrtb2.Imp{{ID: "impID", Ext: nil}}, + }, + { + name: "empty_imp_ext", + sourceIn: &openrtb2.Source{TID: "tid"}, + impIn: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{}`)}}, + expectedSource: &openrtb2.Source{TID: ""}, + expectedImp: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{}`)}}, + }, + { + name: "ext_with_tid", + sourceIn: &openrtb2.Source{TID: "tid"}, + impIn: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{"tid":"123","test":1}`)}}, + expectedSource: &openrtb2.Source{TID: ""}, + expectedImp: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{"test":1}`)}}, + }, + { + name: "ext_without_tid", + sourceIn: &openrtb2.Source{TID: "tid"}, + impIn: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{"data":"123","test":1}`)}}, + expectedSource: &openrtb2.Source{TID: ""}, + expectedImp: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{"data":"123","test":1}`)}}, }, } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Source: test.sourceIn, Imp: test.impIn}} + ScrubTID(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedSource, brw.Source) + assert.Equal(t, test.expectedImp, brw.Imp) + }) + } +} - testIPMasking := getTestIPMasking() +func TestScrubGEO(t *testing.T) { + testCases := []struct { + name string + userIn *openrtb2.User + expectedUser *openrtb2.User + deviceIn *openrtb2.Device + expectedDevice *openrtb2.Device + }{ + { + name: "nil", + userIn: nil, + expectedUser: nil, + deviceIn: nil, + expectedDevice: nil, + }, + { + name: "nil_user_geo", + userIn: &openrtb2.User{ID: "ID", Geo: nil}, + expectedUser: &openrtb2.User{ID: "ID", Geo: nil}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.12}}, + }, + { + name: "with_user_geo", + userIn: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedUser: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: 123.12}}, + deviceIn: &openrtb2.Device{}, + expectedDevice: &openrtb2.Device{}, + }, + { + name: "nil_device_geo", + userIn: &openrtb2.User{}, + expectedUser: &openrtb2.User{}, + deviceIn: &openrtb2.Device{Geo: nil}, + expectedDevice: &openrtb2.Device{Geo: nil}, + }, + { + name: "with_device_geo", + userIn: &openrtb2.User{}, + expectedUser: &openrtb2.User{}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.12}}, + }, + { + name: "with_user_and_device_geo", + userIn: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedUser: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: 123.12}}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.12}}, + }, + } for _, test := range testCases { - t.Run(test.description, func(t *testing.T) { - bidRequest := &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{ - {ID: "testId", Ext: json.RawMessage(`{"test": 1, "tid": 2}`)}, - }, - Source: &openrtb2.Source{ - TID: "testTid", - }, - User: getTestUser(), - Device: getTestDevice(), - } - if test.userExtPresent { - bidRequest.User.Ext = json.RawMessage(`{"data": 1, "eids": 2}`) - } else { - bidRequest.User.Ext = nil - } - bidRequest.User.EIDs = []openrtb2.EID{{Source: "test"}} + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn, Device: test.deviceIn}} + scrubGEO(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + assert.Equal(t, test.expectedDevice, brw.Device) + }) + } +} - result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubRequest(bidRequest, test.enforcement) - assert.Equal(t, test.expected, result, test.description) +func TestScrubGeoFull(t *testing.T) { + testCases := []struct { + name string + userIn *openrtb2.User + expectedUser *openrtb2.User + deviceIn *openrtb2.Device + expectedDevice *openrtb2.Device + }{ + { + name: "nil", + userIn: nil, + expectedUser: nil, + deviceIn: nil, + expectedDevice: nil, + }, + { + name: "nil_user_geo", + userIn: &openrtb2.User{ID: "ID", Geo: nil}, + expectedUser: &openrtb2.User{ID: "ID", Geo: nil}, + deviceIn: &openrtb2.Device{}, + expectedDevice: &openrtb2.Device{}, + }, + { + name: "with_user_geo", + userIn: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedUser: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{}}, + deviceIn: &openrtb2.Device{}, + expectedDevice: &openrtb2.Device{}, + }, + { + name: "nil_device_geo", + userIn: &openrtb2.User{}, + expectedUser: &openrtb2.User{}, + deviceIn: &openrtb2.Device{Geo: nil}, + expectedDevice: &openrtb2.Device{Geo: nil}, + }, + { + name: "with_device_geo", + userIn: &openrtb2.User{}, + expectedUser: &openrtb2.User{}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{}}, + }, + { + name: "with_user_and_device_geo", + userIn: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedUser: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{}}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{}}, + }, + } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn, Device: test.deviceIn}} + scrubGeoFull(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + assert.Equal(t, test.expectedDevice, brw.Device) }) } } @@ -510,42 +404,6 @@ func TestScrubIP(t *testing.T) { bits: 128, maskBits: 96, }, - { - IP: "2001:1db8:0000:0000:0000:ff00:0042:8329", - cleanedIP: "2001:1db8::ff00:42:0", - bits: 128, - maskBits: 112, - }, - { - IP: "2001:1db8:0000:0000:0000:ff00:0042:0", - cleanedIP: "2001:1db8::ff00:42:0", - bits: 128, - maskBits: 112, - }, - { - IP: "127.0.0.1", - cleanedIP: "127.0.0.0", - bits: 32, - maskBits: 24, - }, - { - IP: "0.0.0.0", - cleanedIP: "0.0.0.0", - bits: 32, - maskBits: 24, - }, - { - IP: "192.127.111.134", - cleanedIP: "192.127.111.0", - bits: 32, - maskBits: 24, - }, - { - IP: "192.127.111.0", - cleanedIP: "192.127.111.0", - bits: 32, - maskBits: 24, - }, } for _, test := range testCases { t.Run(test.IP, func(t *testing.T) { @@ -556,32 +414,6 @@ func TestScrubIP(t *testing.T) { } } -func TestScrubGeoFull(t *testing.T) { - geo := &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - } - geoExpected := &openrtb2.Geo{ - Lat: 0, - Lon: 0, - Metro: "", - City: "", - ZIP: "", - } - - result := scrubGeoFull(geo) - - assert.Equal(t, geoExpected, result) -} - -func TestScrubGeoFullWhenNil(t *testing.T) { - result := scrubGeoFull(nil) - assert.Nil(t, result) -} - func TestScrubGeoPrecision(t *testing.T) { geo := &openrtb2.Geo{ Lat: 123.456, @@ -654,26 +486,11 @@ func TestScrubUserExtIDs(t *testing.T) { userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), }, - { - description: "Remove eids Only", - userExt: json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), - expected: json.RawMessage(`{}`), - }, { description: "Remove eids Only - Empty Array", userExt: json.RawMessage(`{"eids":[]}`), expected: json.RawMessage(`{}`), }, - { - description: "Remove eids Only - With Other Data", - userExt: json.RawMessage(`{"anyExisting":42,"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), - expected: json.RawMessage(`{"anyExisting":42}`), - }, - { - description: "Remove eids Only - With Other Nested Data", - userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), - expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), - }, } for _, test := range testCases { @@ -681,52 +498,3 @@ func TestScrubUserExtIDs(t *testing.T) { assert.Equal(t, test.expected, result, test.description) } } - -func getTestUser() *openrtb2.User { - return &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - } -} - -func getTestDevice() *openrtb2.Device { - return &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - } -} - -func getTestIPMasking() config.AccountPrivacy { - return config.AccountPrivacy{ - IPv6Config: config.IPv6{ - 54, - }, - IPv4Config: config.IPv4{ - 24, - }, - } -} From 79d2f27870c928c410c8159947a596a6bd2550e6 Mon Sep 17 00:00:00 2001 From: abermanov-zeta <95416296+abermanov-zeta@users.noreply.github.com> Date: Thu, 16 Nov 2023 19:24:06 +0100 Subject: [PATCH 092/138] Zeta Global SSP: Update endpoints (#3201) --- .../zeta_global_ssp-test/exemplary/banner.json | 2 +- .../zeta_global_ssp-test/exemplary/no-bid.json | 2 +- .../zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json | 2 +- .../zeta_global_ssp-test/supplemental/bad-request.json | 2 +- .../zeta_global_ssp-test/supplemental/invalid-bid-type.json | 2 +- .../zeta_global_ssp-test/supplemental/no-bid-type.json.json | 2 +- .../zeta_global_ssp-test/supplemental/server-error.json | 2 +- adapters/zeta_global_ssp/zeta_global_ssp_test.go | 2 +- static/bidder-info/zeta_global_ssp.yaml | 4 ++-- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json index cdfaf37e45d..8e04138937c 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json index 68aabbed257..3e1ee805552 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json index 248f4b0487a..bc1e496cb27 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "headers": { "Content-Type": [ "application/json;charset=utf-8" diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json index 38f3bd326d0..bf2af3bf25b 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json index 4a55670720d..0aba5f2ca36 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json index 1992586435f..1f5a92c4fdf 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json index 037c7307889..27e71c31255 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ diff --git a/adapters/zeta_global_ssp/zeta_global_ssp_test.go b/adapters/zeta_global_ssp/zeta_global_ssp_test.go index f5384cdec0f..fabfa5efed8 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp_test.go +++ b/adapters/zeta_global_ssp/zeta_global_ssp_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderZetaGlobalSsp, config.Adapter{ - Endpoint: "http://whatever.url"}, + Endpoint: "https://ssp.disqus.com/bid/prebid-server?sid=11"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { diff --git a/static/bidder-info/zeta_global_ssp.yaml b/static/bidder-info/zeta_global_ssp.yaml index 3cc2ecb5297..479f024c1b7 100644 --- a/static/bidder-info/zeta_global_ssp.yaml +++ b/static/bidder-info/zeta_global_ssp.yaml @@ -1,4 +1,4 @@ -endpoint: https://ssp.disqus.com/bid/prebid-server?sid=GET_SID_FROM_ZETA&shortname=GET_SHORTNAME_FROM_ZETA +endpoint: https://ssp.disqus.com/bid/prebid-server?sid=GET_SID_FROM_ZETA endpointCompression: gzip maintainer: email: DL-Zeta-SSP@zetaglobal.com @@ -15,6 +15,6 @@ capabilities: - video userSync: redirect: - url: https://ssp.disqus.com/redirectuser?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}&partner=GET_SHORTNAME_FROM_ZETA + url: https://ssp.disqus.com/redirectuser?sid=GET_SID_FROM_ZETA&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}} userMacro: 'BUYERUID' From 832fbf2d7c3f207d0e86807536045369b79b30d1 Mon Sep 17 00:00:00 2001 From: Nilesh Chate <97721111+pm-nilesh-chate@users.noreply.github.com> Date: Fri, 17 Nov 2023 00:23:11 +0530 Subject: [PATCH 093/138] PubMatic: Support hardcoded alias (#3224) --- adapters/pubmatic/pubmatic.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index 17f43ea0f0a..7ab0248b937 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -22,7 +22,8 @@ const MAX_IMPRESSIONS_PUBMATIC = 30 const ae = "ae" type PubmaticAdapter struct { - URI string + URI string + bidderName string } type pubmaticBidExt struct { @@ -640,7 +641,8 @@ func getBidType(bidExt *pubmaticBidExt) openrtb_ext.BidType { // Builder builds a new instance of the Pubmatic adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &PubmaticAdapter{ - URI: config.Endpoint, + URI: config.Endpoint, + bidderName: string(bidderName), } return bidder, nil } From 9693797b3d0eb81c71e86c967b5604d202b9a269 Mon Sep 17 00:00:00 2001 From: gg-natalia <148577437+gg-natalia@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:59:47 -0300 Subject: [PATCH 094/138] GumGum: Add product parameter (#3253) --- adapters/gumgum/gumgum.go | 18 ++- .../banner-with-pubId-product-params.json | 104 ++++++++++++++++++ adapters/gumgum/params_test.go | 4 + openrtb_ext/imp_gumgum.go | 9 +- static/bidder-params/gumgum.json | 6 +- 5 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 adapters/gumgum/gumgumtest/supplemental/banner-with-pubId-product-params.json diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go index 3b954bf5837..ea9624c73c7 100644 --- a/adapters/gumgum/gumgum.go +++ b/adapters/gumgum/gumgum.go @@ -14,13 +14,13 @@ import ( "github.com/prebid/prebid-server/v2/openrtb_ext" ) -// GumGumAdapter implements Bidder interface. -type GumGumAdapter struct { +// adapter implements Bidder interface. +type adapter struct { URI string } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (g *GumGumAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (g *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var validImps []openrtb2.Imp var siteCopy openrtb2.Site if request.Site != nil { @@ -80,7 +80,7 @@ func (g *GumGumAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adap } // MakeBids unpacks the server's response into Bids. -func (g *GumGumAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (g *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -176,6 +176,14 @@ func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) { } } + if gumgumExt.Product != "" { + var err error + imp.Ext, err = json.Marshal(map[string]string{"product": gumgumExt.Product}) + if err != nil { + return nil, err + } + } + return &gumgumExt, nil } @@ -223,7 +231,7 @@ func validateVideoParams(video *openrtb2.Video) (err error) { // Builder builds a new instance of the GumGum adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &GumGumAdapter{ + bidder := &adapter{ URI: config.Endpoint, } return bidder, nil diff --git a/adapters/gumgum/gumgumtest/supplemental/banner-with-pubId-product-params.json b/adapters/gumgum/gumgumtest/supplemental/banner-with-pubId-product-params.json new file mode 100644 index 00000000000..6e2793658d3 --- /dev/null +++ b/adapters/gumgum/gumgumtest/supplemental/banner-with-pubId-product-params.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pubId": 12345678 + }, + "product": "skins" + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://g2.gumgum.com/providers/prbds2s/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pubId": 12345678 + }, + "product": "skins" + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "2068416", + "adm": "some-test-ad", + "adid": "2068416", + "price": 5, + "id": "5736a50b-6b05-42a8-aa6d-b0a4649dcd05", + "impid": "test-imp-id", + "cid": "4747" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "2068416", + "adm": "some-test-ad", + "adid": "2068416", + "price": 5, + "id": "5736a50b-6b05-42a8-aa6d-b0a4649dcd05", + "impid": "test-imp-id", + "cid": "4747" + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/gumgum/params_test.go b/adapters/gumgum/params_test.go index afee017be03..11874cdbd61 100644 --- a/adapters/gumgum/params_test.go +++ b/adapters/gumgum/params_test.go @@ -37,11 +37,13 @@ var validParams = []string{ `{"zone":"dc9d6be1"}`, `{"pubId":12345678}`, `{"zone":"dc9d6be1", "pubId":12345678}`, + `{"zone":"dc9d6be1", "pubId":12345678, "product": "skins"}`, `{"zone":"dc9d6be1", "slot":1234567}`, `{"pubId":12345678, "slot":1234567}`, `{"pubId":12345678, "irisid": "iris_6f9285823a48bne5"}`, `{"zone":"dc9d6be1", "irisid": "iris_6f9285823a48bne5"}`, `{"zone":"dc9d6be1", "pubId":12345678, "irisid": "iris_6f9285823a48bne5"}`, + `{"zone":"dc9d6be1", "pubId":12345678, "irisid": "iris_6f9285823a48bne5", "product": "skins"}`, } var invalidParams = []string{ @@ -62,4 +64,6 @@ var invalidParams = []string{ `{"zone":"1234567", "irisid": ""}`, `{"zone":"1234567", "irisid": 1234}`, `{"irisid": "iris_6f9285823a48bne5"}`, + `{"product": "test"}`, + `{"product": 12345678}`, } diff --git a/openrtb_ext/imp_gumgum.go b/openrtb_ext/imp_gumgum.go index 96a1308d663..f54234fa394 100644 --- a/openrtb_ext/imp_gumgum.go +++ b/openrtb_ext/imp_gumgum.go @@ -3,10 +3,11 @@ package openrtb_ext // ExtImpGumGum defines the contract for bidrequest.imp[i].ext.prebid.bidder.gumgum // Either Zone or PubId must be present, others are optional parameters type ExtImpGumGum struct { - Zone string `json:"zone,omitempty"` - PubID float64 `json:"pubId,omitempty"` - IrisID string `json:"irisid,omitempty"` - Slot float64 `json:"slot,omitempty"` + Zone string `json:"zone,omitempty"` + PubID float64 `json:"pubId,omitempty"` + IrisID string `json:"irisid,omitempty"` + Slot float64 `json:"slot,omitempty"` + Product string `json:"product,omitempty"` } // ExtImpGumGumVideo defines the contract for bidresponse.seatbid.bid[i].ext.prebid.bidder.gumgum.video diff --git a/static/bidder-params/gumgum.json b/static/bidder-params/gumgum.json index 95f05e7d517..c9972713f87 100644 --- a/static/bidder-params/gumgum.json +++ b/static/bidder-params/gumgum.json @@ -20,6 +20,10 @@ "slot": { "type": "integer", "description": "A slot id used to identify a slot placement mapped to a GumGum zone or publisher" + }, + "product": { + "type": "string", + "description": "Product param that allow support for Desktop Skins - display and video" } }, "anyOf": [ @@ -34,4 +38,4 @@ ] } ] -} +} \ No newline at end of file From 738884f7cc4182f712d679df800f13e847f1ccac Mon Sep 17 00:00:00 2001 From: Sonali-More-Xandr <87759626+Sonali-More-Xandr@users.noreply.github.com> Date: Sat, 18 Nov 2023 02:36:36 +0530 Subject: [PATCH 095/138] Migrate yahooAdvertising alias of yahooAds to use new pattern (#3203) --- exchange/adapter_builders.go | 1 - openrtb_ext/bidders.go | 2 -- openrtb_ext/bidders_validate_test.go | 2 +- static/bidder-info/yahooAdvertising.yaml | 14 +------------- static/bidder-params/yahooAdvertising.json | 19 ------------------- 5 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 static/bidder-params/yahooAdvertising.json diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index a5179f75d53..2f422ede534 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -382,7 +382,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderVrtcal: vrtcal.Builder, openrtb_ext.BidderXeworks: xeworks.Builder, openrtb_ext.BidderYahooAds: yahooAds.Builder, - openrtb_ext.BidderYahooAdvertising: yahooAds.Builder, openrtb_ext.BidderYahooSSP: yahooAds.Builder, openrtb_ext.BidderYeahmobi: yeahmobi.Builder, openrtb_ext.BidderYieldlab: yieldlab.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index fbd5bb9f787..77113bf5bda 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -204,7 +204,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderVrtcal, BidderXeworks, BidderYahooAds, - BidderYahooAdvertising, BidderYahooSSP, BidderYeahmobi, BidderYieldlab, @@ -486,7 +485,6 @@ const ( BidderVrtcal BidderName = "vrtcal" BidderXeworks BidderName = "xeworks" BidderYahooAds BidderName = "yahooAds" - BidderYahooAdvertising BidderName = "yahooAdvertising" BidderYahooSSP BidderName = "yahoossp" BidderYeahmobi BidderName = "yeahmobi" BidderYieldlab BidderName = "yieldlab" diff --git a/openrtb_ext/bidders_validate_test.go b/openrtb_ext/bidders_validate_test.go index 33148d1ed41..fed606ea439 100644 --- a/openrtb_ext/bidders_validate_test.go +++ b/openrtb_ext/bidders_validate_test.go @@ -50,7 +50,7 @@ func TestBidderUniquenessGatekeeping(t *testing.T) { // - Exclude duplicates of adapters for the same bidder, as it's unlikely a publisher will use both. var bidders []string for _, bidder := range CoreBidderNames() { - if bidder != BidderSilverPush && bidder != BidderTripleliftNative && bidder != BidderAdkernelAdn && bidder != BidderFreewheelSSPOld && bidder != BidderYahooAdvertising { + if bidder != BidderSilverPush && bidder != BidderTripleliftNative && bidder != BidderAdkernelAdn && bidder != BidderFreewheelSSPOld && bidder != "yahooAdvertising" { bidders = append(bidders, string(bidder)) } } diff --git a/static/bidder-info/yahooAdvertising.yaml b/static/bidder-info/yahooAdvertising.yaml index 3798adf7ca2..f9f34fbfbd0 100644 --- a/static/bidder-info/yahooAdvertising.yaml +++ b/static/bidder-info/yahooAdvertising.yaml @@ -1,16 +1,4 @@ -endpoint: "https://s2shb.ssp.yahoo.com/admax/bid/partners/PBS" -maintainer: - email: "hb-fe-tech@yahooinc.com" -gvlVendorID: 25 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video +aliasOf: yahooAds userSync: # yahooAdvertising supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-params/yahooAdvertising.json b/static/bidder-params/yahooAdvertising.json deleted file mode 100644 index 4778f0778c7..00000000000 --- a/static/bidder-params/yahooAdvertising.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "YahooAdvertising Adapter Params", - "description": "A schema which validates params accepted by the YahooAdvertising adapter", - "type": "object", - "properties": { - "dcn": { - "type": "string", - "minLength": 1, - "description": "Site ID provided by One Mobile" - }, - "pos": { - "type": "string", - "minLength": 1, - "description": "Placement ID" - } - }, - "required": ["dcn", "pos"] -} From 21ee27b948dc501184ea666847de14cbeb1e9de3 Mon Sep 17 00:00:00 2001 From: Sonali-More-Xandr <87759626+Sonali-More-Xandr@users.noreply.github.com> Date: Sat, 18 Nov 2023 02:49:54 +0530 Subject: [PATCH 096/138] Migrate synacormedia alias of imds to use new pattern (#3205) --- exchange/adapter_builders.go | 1 - openrtb_ext/bidders.go | 2 -- static/bidder-info/synacormedia.yaml | 14 +------------- static/bidder-params/synacormedia.json | 19 ------------------- 4 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 static/bidder-params/synacormedia.json diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 2f422ede534..594833401ba 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -359,7 +359,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderSspBC: sspBC.Builder, openrtb_ext.BidderSuntContent: suntContent.Builder, openrtb_ext.BidderStroeerCore: stroeerCore.Builder, - openrtb_ext.BidderSynacormedia: imds.Builder, openrtb_ext.BidderTaboola: taboola.Builder, openrtb_ext.BidderTappx: tappx.Builder, openrtb_ext.BidderTeads: teads.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 77113bf5bda..77e58fcd8b1 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -181,7 +181,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderSspBC, BidderStroeerCore, BidderSuntContent, - BidderSynacormedia, BidderTaboola, BidderTappx, BidderTeads, @@ -462,7 +461,6 @@ const ( BidderSspBC BidderName = "sspBC" BidderStroeerCore BidderName = "stroeerCore" BidderSuntContent BidderName = "suntContent" - BidderSynacormedia BidderName = "synacormedia" BidderTaboola BidderName = "taboola" BidderTappx BidderName = "tappx" BidderTeads BidderName = "teads" diff --git a/static/bidder-info/synacormedia.yaml b/static/bidder-info/synacormedia.yaml index 2a796ae839f..ccbba0700a4 100644 --- a/static/bidder-info/synacormedia.yaml +++ b/static/bidder-info/synacormedia.yaml @@ -1,15 +1,3 @@ # DEPRECATED: Use imds bidder instead +aliasOf: imds endpoint: "https://pbs.technoratimedia.com/openrtb/bids/{{.AccountID}}?src={{.SourceId}}&adapter=synacormedia" -maintainer: - email: "eng-demand@imds.tv" -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video -userSync: - key: "imds" diff --git a/static/bidder-params/synacormedia.json b/static/bidder-params/synacormedia.json deleted file mode 100644 index 21eb06cc7ce..00000000000 --- a/static/bidder-params/synacormedia.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Synacormedia Adapter Params", - "description": "DEPRECATED: Use imds bidder instead. A schema which validates params accepted by the Synacormedia adapter", - - "type": "object", - "properties": { - "seatId": { - "type": "string", - "description": "The seat id." - }, - "tagId": { - "type": "string", - "description": "The tag id." - } - }, - - "required": ["seatId"] -} From 7e06aae947489da90eb3020e4cd36a62bb9485ca Mon Sep 17 00:00:00 2001 From: Sonali-More-Xandr <87759626+Sonali-More-Xandr@users.noreply.github.com> Date: Sat, 18 Nov 2023 03:37:05 +0530 Subject: [PATCH 097/138] Migrate epsilon alias of conversant to use new pattern (#3207) --- exchange/adapter_builders.go | 1 - openrtb_ext/bidders.go | 2 -- static/bidder-info/epsilon.yaml | 14 +------- static/bidder-params/epsilon.json | 54 ------------------------------- 4 files changed, 1 insertion(+), 70 deletions(-) delete mode 100644 static/bidder-params/epsilon.json diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 594833401ba..b859af01148 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -274,7 +274,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderEmxDigital: cadentaperturemx.Builder, openrtb_ext.BidderEPlanning: eplanning.Builder, openrtb_ext.BidderEpom: epom.Builder, - openrtb_ext.BidderEpsilon: conversant.Builder, openrtb_ext.BidderEVolution: evolution.Builder, openrtb_ext.BidderFlipp: flipp.Builder, openrtb_ext.BidderFreewheelSSP: freewheelssp.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 77e58fcd8b1..a921f981aee 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -96,7 +96,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderEmxDigital, BidderEPlanning, BidderEpom, - BidderEpsilon, BidderEVolution, BidderFlipp, BidderFreewheelSSP, @@ -375,7 +374,6 @@ const ( BidderEmtv BidderName = "emtv" BidderEmxDigital BidderName = "emx_digital" BidderEPlanning BidderName = "eplanning" - BidderEpsilon BidderName = "epsilon" BidderEpom BidderName = "epom" BidderEVolution BidderName = "e_volution" BidderFlipp BidderName = "flipp" diff --git a/static/bidder-info/epsilon.yaml b/static/bidder-info/epsilon.yaml index 856d82a6f3f..828bac6cfc4 100644 --- a/static/bidder-info/epsilon.yaml +++ b/static/bidder-info/epsilon.yaml @@ -1,16 +1,4 @@ -endpoint: "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25" -maintainer: - email: "PublisherIntegration@epsilon.com" -gvlVendorID: 24 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video +aliasOf: conversant userSync: redirect: url: "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl={{.RedirectURL}}" diff --git a/static/bidder-params/epsilon.json b/static/bidder-params/epsilon.json deleted file mode 100644 index a33a68325e4..00000000000 --- a/static/bidder-params/epsilon.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Epsilon Adapter Params", - "description": "A schema which validates params accepted by the Epsilon adapter.", - "type": "object", - "properties": { - "site_id": { - "type": "string", - "description": "An Epsilon specific ID which identifies the site." - }, - "secure": { - "type": "integer", - "description": "Override http/https context on ad markup." - }, - "bidfloor" : { - "type": "number", - "description": "Minimum bid price that will be considered." - }, - "tag_id": { - "type": "string", - "description": "Identifies specific ad placement." - }, - "position": { - "type": "integer", - "description": "Ad position on screen." - }, - "mimes": { - "type": "array", - "description": "Array of content MIME types. For videos only.", - "items": { - "type": "string" - } - }, - "maxduration": { - "type": "integer", - "description": "Maximum duration in seconds. For videos only." - }, - "api": { - "type": "array", - "description": "Array of supported API frameworks. For videos only.", - "items": { - "type": "integer" - } - }, - "protocols": { - "type": "array", - "description": "Array of supported video protocols. For videos only.", - "items": { - "type": "integer" - } - } - }, - "required": ["site_id"] -} \ No newline at end of file From f5f1e072180de52d1f17d9f51cc5e7c38785ca99 Mon Sep 17 00:00:00 2001 From: BrightMountainMedia <69471268+BrightMountainMediaInc@users.noreply.github.com> Date: Sat, 18 Nov 2023 03:45:53 +0530 Subject: [PATCH 098/138] BMTM: app support (#3280) --- .../exemplary/app-banner.json | 105 ++++++++++++++++ .../exemplary/app-video.json | 115 ++++++++++++++++++ static/bidder-info/bmtm.yaml | 4 + 3 files changed, 224 insertions(+) create mode 100644 adapters/bmtm/brightmountainmediatest/exemplary/app-banner.json create mode 100644 adapters/bmtm/brightmountainmediatest/exemplary/app-video.json diff --git a/adapters/bmtm/brightmountainmediatest/exemplary/app-banner.json b/adapters/bmtm/brightmountainmediatest/exemplary/app-banner.json new file mode 100644 index 00000000000..8a02ad8d5ea --- /dev/null +++ b/adapters/bmtm/brightmountainmediatest/exemplary/app-banner.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placement_id": 329 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/api/pbs", + "body": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "329" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/bmtm/brightmountainmediatest/exemplary/app-video.json b/adapters/bmtm/brightmountainmediatest/exemplary/app-video.json new file mode 100644 index 00000000000..1dc1a4752f5 --- /dev/null +++ b/adapters/bmtm/brightmountainmediatest/exemplary/app-video.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placement_id": 329 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/api/pbs", + "body": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ], + "w": 1024, + "h": 576 + }, + "tagid": "329" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/static/bidder-info/bmtm.yaml b/static/bidder-info/bmtm.yaml index 2992d0fbc7b..3d3f2ca6e22 100644 --- a/static/bidder-info/bmtm.yaml +++ b/static/bidder-info/bmtm.yaml @@ -3,6 +3,10 @@ maintainer: email: dev@brightmountainmedia.com modifyingVastXmlAllowed: false capabilities: + app: + mediaTypes: + - banner + - video site: mediaTypes: - banner From 515b9372f850f856c8c2f8875c624aa9b9816512 Mon Sep 17 00:00:00 2001 From: Ahmet Faruk Karakus <65093478+ahmetfaruk59@users.noreply.github.com> Date: Mon, 20 Nov 2023 09:06:20 +0300 Subject: [PATCH 099/138] HuaweiAds: Increase fill rate for native ads (#3279) co-authored by @ahmetfaruk59 --- adapters/huaweiads/huaweiads.go | 87 +++-- .../exemplary/nativeIncludeVideo.json | 114 ++++--- .../exemplary/nativeMultiSizesByRange.json | 305 ++++++++++++++++++ .../exemplary/nativeMultiSizesByRatio.json | 305 ++++++++++++++++++ .../exemplary/nativeSingleImage.json | 67 +++- .../exemplary/nativeThreeImage.json | 21 +- .../nativeThreeImageIncludeIcon.json | 71 +++- .../supplemental/bad_response_not_native.json | 67 +++- 8 files changed, 951 insertions(+), 86 deletions(-) create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRange.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRatio.json diff --git a/adapters/huaweiads/huaweiads.go b/adapters/huaweiads/huaweiads.go index 68622bf9f9e..ac4e2446bdc 100644 --- a/adapters/huaweiads/huaweiads.go +++ b/adapters/huaweiads/huaweiads.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "math" "net/http" "net/url" "regexp" @@ -533,11 +534,27 @@ func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error { return err } + //popular size for native ads + popularSizes := []map[string]int64{{"w": 225, "h": 150}, {"w": 300, "h": 250}, {"w": 320, "h": 250}, {"w": 640, "h": 360}, {"w": 720, "h": 1280}, {"w": 1080, "h": 170}, {"w": 1080, "h": 430}, {"w": 1080, "h": 432}, {"w": 1080, "h": 504}, {"w": 1080, "h": 607}, {"w": 1080, "h": 1620}, {"w": 1200, "h": 627}, {"w": 1280, "h": 720}, {"w": 1312, "h": 768}, {"w": 1920, "h": 1080}} + // only compute the main image number, type = native1.ImageAssetTypeMain var numMainImage = 0 var numVideo = 0 - var width int64 - var height int64 + var formats = make([]format, 0) + var numFormat = 0 + var detailedCreativeTypeList = make([]string, 0, 2) + + //number of the requested image size + for _, asset := range nativePayload.Assets { + if numFormat > 1 { + break + } + if asset.Img != nil { + if asset.Img.Type == native1.ImageAssetTypeMain { + numFormat++ + } + } + } for _, asset := range nativePayload.Assets { // Only one of the {title,img,video,data} objects should be present in each object. if asset.Video != nil { @@ -548,34 +565,52 @@ func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error { if asset.Img != nil { if asset.Img.Type == native1.ImageAssetTypeMain { numMainImage++ - if asset.Img.H != 0 && asset.Img.W != 0 { - width = asset.Img.W - height = asset.Img.H - } else if asset.Img.WMin != 0 && asset.Img.HMin != 0 { - width = asset.Img.WMin - height = asset.Img.HMin + if numFormat > 1 && asset.Img.H != 0 && asset.Img.W != 0 && asset.Img.WMin != 0 && asset.Img.HMin != 0 { + formats = append(formats, format{asset.Img.W, asset.Img.H}) + } + if numFormat == 1 && asset.Img.H != 0 && asset.Img.W != 0 && asset.Img.WMin != 0 && asset.Img.HMin != 0 { + result := filterPopularSizes(popularSizes, asset.Img.W, asset.Img.H, "ratio") + for i := 0; i < len(result); i++ { + formats = append(formats, format{result[i]["w"], result[i]["h"]}) + } + } + if numFormat == 1 && asset.Img.H == 0 && asset.Img.W == 0 && asset.Img.WMin != 0 && asset.Img.HMin != 0 { + result := filterPopularSizes(popularSizes, asset.Img.W, asset.Img.H, "range") + for i := 0; i < len(result); i++ { + formats = append(formats, format{result[i]["w"], result[i]["h"]}) + } } } - continue } + adslot30.Format = formats } - adslot30.W = width - adslot30.H = height - - var detailedCreativeTypeList = make([]string, 0, 2) - if numVideo >= 1 { - detailedCreativeTypeList = append(detailedCreativeTypeList, "903") - } else if numMainImage > 1 { - detailedCreativeTypeList = append(detailedCreativeTypeList, "904") - } else if numMainImage == 1 { - detailedCreativeTypeList = append(detailedCreativeTypeList, "901") - } else { - detailedCreativeTypeList = append(detailedCreativeTypeList, "913", "914") - } + detailedCreativeTypeList = append(detailedCreativeTypeList, "901", "905") adslot30.DetailedCreativeTypeList = detailedCreativeTypeList return nil } +// filter popular size by range or ratio to append format array +func filterPopularSizes(sizes []map[string]int64, width int64, height int64, byWhat string) []map[string]int64 { + + filtered := []map[string]int64{} + for _, size := range sizes { + w := size["w"] + h := size["h"] + + if byWhat == "ratio" { + ratio := float64(width) / float64(height) + diff := math.Abs(float64(w)/float64(h) - ratio) + if diff <= 0.3 { + filtered = append(filtered, size) + } + } + if byWhat == "range" && w > width && h > height { + filtered = append(filtered, size) + } + } + return filtered +} + // roll ad need TotalDuration func getVideoFormat(adslot30 *adslot30, adtype int32, openRTBImp *openrtb2.Imp) error { adslot30.W = openRTBImp.Video.W @@ -960,10 +995,14 @@ func checkRespStatusCode(response *adapters.ResponseData) error { } func checkHuaweiAdsResponseRetcode(response huaweiAdsResponse) error { - if response.Retcode == 200 || response.Retcode == 204 || response.Retcode == 206 { + if response.Retcode == 200 || response.Retcode == 206 { return nil } - + if response.Retcode == 204 { + return &errortypes.BadInput{ + Message: fmt.Sprintf("HuaweiAdsResponse retcode: %d , reason: The request packet is correct, but no advertisement was found for this request.", response.Retcode), + } + } if (response.Retcode < 600 && response.Retcode >= 400) || (response.Retcode < 300 && response.Retcode > 200) { return &errortypes.BadInput{ Message: fmt.Sprintf("HuaweiAdsResponse retcode: %d , reason: %s", response.Retcode, response.Reason), diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json index 673d6a39a99..49562dacc80 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json @@ -5,7 +5,7 @@ { "id": "test-imp-id", "native": { - "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":107,\"video\":{\"mimes\":[\"mp4\"],\"minduration\":100,\"maxduration\":100,\"protocols\":[1,2]},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"w\":200,\"h\":300,\"wmin\":300,\"hmin\":200},\"required\":1},{\"id\":102,\"img\":{\"type\":3,\"w\":400,\"h\":500,\"wmin\":400,\"hmin\":500},\"required\":1},{\"id\":103,\"img\":{\"type\":3,\"w\":300,\"h\":250,\"wmin\":300,\"hmin\":250},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", "ver": "1.2" }, "ext": { @@ -21,15 +21,14 @@ } ], "app": { - "bundle": "com.huawei.browser", + "bundle": "com.huawei.p11", "name": "Huawei Browser", "ver": "9.1.0.301" }, "device": { "ua": "useragent", "h": 1920, - "language": "zh", - "geoCountry": "CH", + "language": "en", "model": "COL-TEST", "os": "android", "osv": "10.0.0", @@ -39,12 +38,10 @@ "ip": "152.193.6.74", "pxratio": 23.01, "geo": { - "country": "" + "country": "ARG" } }, "user": { - "id": "db089de9-a62e-4861-a881-0ff15e052516", - "buyeruid": "v4_bidder_token", "ext": { "data": { "gaid": [ @@ -83,9 +80,9 @@ "body": { "app": { "lang": "en", - "country": "ZA", + "country": "AR", "name": "Huawei Browser", - "pkgname": "com.huawei.browser", + "pkgname": "com.example.pkgname2", "version": "9.1.0.301" }, "multislot": [ @@ -93,21 +90,34 @@ "adtype": 3, "slotid": "u42ohmaufh", "detailedCreativeTypeList": [ - "903" + "901", + "905" ], - "h": 200, - "w": 200, + "format": [ + { + "w": 200, + "h": 300 + }, + { + "w": 400, + "h": 500 + }, + { + "w": 300, + "h": 250 + } + ], "test": 1 } ], "device": { "height": 1920, - "language": "zh", + "language": "en", "oaid": "oaid", "os": "android", "type": 4, "ip": "152.193.6.74", - "localeCountry": "ZA", + "localeCountry": "AR", "pxratio": 23.01, "model": "COL-TEST", "width": 1080, @@ -116,7 +126,7 @@ "useragent": "useragent", "version": "10.0.0", "maker": "huawei", - "belongCountry": "ZA" + "belongCountry": "AR" }, "geo": { }, @@ -146,7 +156,7 @@ "metaData": { "adSign": "2", "apkInfo": { - "appIcon": "https://pps-icon.png", + "appIcon": "https://icon.png", "appName": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", "fileSize": 118902470, "packageName": "com.demo.package", @@ -171,7 +181,16 @@ "height": 160, "imageType": "img", "sha256": "042479eccbda9a8d7d3aa3da73c42486854407835623a30ffff875cb578242d0", - "url": "https://pps-icon.png", + "url": "https://icon1.png", + "width": 160 + }, + { + "checkSha256Flag": 1, + "fileSize": 10797, + "height": 320, + "imageType": "img", + "sha256": "042479eccbda9a8d7d3aa3da73c42486854407835623a30ffff875cb578242d0", + "url": "https://icon2.png", "width": 160 } ], @@ -191,49 +210,51 @@ "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", "url": "http://image2.jpg", "width": 400 + }, + { + "checkSha256Flag": 0, + "height": 300, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image3.jpg", + "width": 400 } ], "label": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", "landingPageType": "3", "marketAppId": "C101219405", "title": "%2Ftest%2F", - "description": "this is a test ad", - "videoInfo": { - "autoPlayAreaRatio": 100, - "autoStopPlayAreaRatio": 10, - "checkSha256Flag": 1, - "sha256": "aa08c8ffce82bbcd37cabefd6c8972b407de48f0b4e332e06d4cc18d25377d77", - "timeBeforeVideoAutoPlay": 50, - "videoAutoPlayOnWifi": "y", - "videoAutoPlayWithSound": "n", - "videoDownloadUrl": "https://video.mp4", - "videoDuration": 6038, - "videoFileSize": 949951, - "videoPlayMode": 2, - "videoRatio": 0.5625, - "width": 600, - "height": 500 - } + "description": "this is a test ad" }, "monitor": [ { - "eventType": "vastError", + "eventType": "click", "url": [ - "http://test/vastError" + "http://test/click" ] }, { - "eventType": "click", + "eventType": "imp", "url": [ - "http://test/click", - "http://test/dspclick" + "http://test/imp" ] }, { - "eventType": "imp", + "eventType": "download", + "url": [ + "http://test/download" + ] + }, + { + "eventType": "install", + "url": [ + "http://test/install" + ] + }, + { + "eventType": "downloadstart", "url": [ - "http://test/imp", - "http://test/dspimp" + "http://test/downloadstart" ] }, { @@ -251,8 +272,7 @@ { "eventType": "playEnd", "url": [ - "http://test/playEnd1", - "http://test/playEnd2" + "http://test/playEnd" ] }, { @@ -312,12 +332,12 @@ "huaweiads" ], "crid": "58022259", - "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":101,\"img\":{\"type\":3,\"url\":\"http://image1.jpg\",\"w\":400,\"h\":350}},{\"id\":107,\"video\":{\"vasttag\":\"HuaweiAds/test/00:00:06.038 \"}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"this is a test ad\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\",\"http://test/dspclick\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"},{\"event\":1,\"method\":1,\"url\":\"http://test/dspimp\"}]}", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":101,\"img\":{\"type\":3,\"url\":\"http://image1.jpg\",\"w\":400,\"h\":350}},{\"id\":102,\"img\":{\"type\":3,\"url\":\"http://image2.jpg\",\"w\":400,\"h\":300}},{\"id\":103,\"img\":{\"type\":3,\"url\":\"http://image3.jpg\",\"w\":400,\"h\":300}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"this is a test ad\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}", "id": "test-imp-id", "impid": "test-imp-id", "price": 2.8, - "h": 500, - "w": 600 + "h": 350, + "w": 400 }, "type": "native" } diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRange.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRange.json new file mode 100644 index 00000000000..e9128ce1619 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRange.json @@ -0,0 +1,305 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":101,\"title\":{\"len\":90},\"required\":1},{\"id\":102,\"img\":{\"type\":3,\"wmin\":225,\"hmin\":150},\"required\":1},{\"id\":103,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "slotid": "u42ohmaufh", + "adtype": "native", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.huawei.browser", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "en", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "dnt": 0, + "pxratio": 23.01, + "geo": { + "country": "DEU" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://adx-dre.op.hicloud.com/ppsadx/getResult", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "DE", + "name": "Huawei Browser", + "pkgname": "com.huawei.browser", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 3, + "slotid": "u42ohmaufh", + "detailedCreativeTypeList": [ + "901", + "905" + ], + "format":[{"w":225,"h":150},{"w":300,"h":250},{"w":320,"h":250},{"w":640,"h":360},{"w":720,"h":1280},{"w":1080,"h":170},{"w":1080,"h":430},{"w":1080,"h":432},{"w":1080,"h":504},{"w":1080,"h":607},{"w":1080,"h":1620},{"w":1200,"h":627},{"w":1280,"h":720},{"w":1312,"h":768},{"w":1920,"h":1080}], + "test": 1 + } + ], + "device": { + "height": 1920, + "language": "en", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "DE", + "pxratio": 23.01, + "model": "COL-TEST", + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "gaidTrackingEnabled": "1", + "isTrackingEnabled": "1", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "belongCountry": "DE" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "dsp1cost": 28, + "dspcost": 59, + "multiad": [ + { + "adtype": 3, + "content": [ + { + "contentid": "58022259", + "creativetype": 106, + "ctrlSwitchs": "001011101001010212", + "endtime": 1621344684645, + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "apkInfo": { + "appDesc": "43+%E4%BA%BF%E6%AC%A1%E5%AE%89%E8%A3%85", + "appIcon": "https://icon.png", + "appName": "demo", + "fileSize": 118902470, + "packageName": "com.demo.package", + "permPromptForCard": "0", + "popNotify": 1, + "popUpAfterInstallNew": 1, + "priorInstallWay": "2", + "sha256": "sha256", + "url": "http://test/url", + "versionCode": "284", + "versionName": "9.6.1.9" + }, + "appId": "101219405", + "appPromotionChannel": "401721412", + "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "cta": "install", + "duration": 6038, + "description": "", + "imageInfo": [ + { + "checkSha256Flag": 0, + "height": 607, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image.jpg", + "width": 1080 + } + ], + "label": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "landingPageType": "3", + "marketAppId": "C101219405", + "title": "%2Ftest%2F" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp" + ] + }, + { + "eventType": "download", + "url": [ + "http://test/download" + ] + }, + { + "eventType": "install", + "url": [ + "http://test/install" + ] + }, + { + "eventType": "downloadstart", + "url": [ + "http://test/downloadstart" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + }, + { + "eventType": "playStart", + "url": [ + "http://test/playStart" + ] + }, + { + "eventType": "playEnd", + "url": [ + "http://test/playEnd" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "u42ohmaufh" + } + ], + "noReportAdTypeEventList": [ + { + "adType": 3, + "eventTypeList": [ + "installFail" + ] + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adomain": [ + "huaweiads" + ], + "crid": "58022259", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":101,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":102,\"img\":{\"type\":3,\"url\":\"http://image.jpg\",\"w\":1080,\"h\":607}},{\"id\":103,\"data\":{\"label\":\"desc\",\"value\":\"\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}", + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 2.8, + "h": 607, + "w": 1080 + }, + "type": "native" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRatio.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRatio.json new file mode 100644 index 00000000000..cd21f40ee69 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRatio.json @@ -0,0 +1,305 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":101,\"title\":{\"len\":90},\"required\":1},{\"id\":102,\"img\":{\"type\":3,\"w\":225,\"wmin\":225,\"h\":150,\"hmin\":150},\"required\":1},{\"id\":103,\"data\":{\"type\":2,\"len\":90},\"required\":1},{\"id\":104,\"data\":{\"type\":12,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "slotid": "u42ohmaufh", + "adtype": "native", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.huawei.browser", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "en", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "dnt": 0, + "pxratio": 23.01, + "geo": { + "country": "DEU" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://adx-dre.op.hicloud.com/ppsadx/getResult", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "DE", + "name": "Huawei Browser", + "pkgname": "com.huawei.browser", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 3, + "slotid": "u42ohmaufh", + "detailedCreativeTypeList": [ + "901", + "905" + ], + "format": [{"w":225,"h":150},{"w":320,"h":250},{"w":640,"h":360},{"w":1080,"h":607},{"w":1280,"h":720},{"w":1312,"h":768},{"w":1920,"h":1080}], + "test": 1 + } + ], + "device": { + "height": 1920, + "language": "en", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "DE", + "pxratio": 23.01, + "model": "COL-TEST", + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "gaidTrackingEnabled": "1", + "isTrackingEnabled": "1", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "belongCountry": "DE" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "dsp1cost": 28, + "dspcost": 59, + "multiad": [ + { + "adtype": 3, + "content": [ + { + "contentid": "58022259", + "creativetype": 106, + "ctrlSwitchs": "001011101001010212", + "endtime": 1621344684645, + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "apkInfo": { + "appDesc": "43+%E4%BA%BF%E6%AC%A1%E5%AE%89%E8%A3%85", + "appIcon": "https://icon.png", + "appName": "demo", + "fileSize": 118902470, + "packageName": "com.demo.package", + "permPromptForCard": "0", + "popNotify": 1, + "popUpAfterInstallNew": 1, + "priorInstallWay": "2", + "sha256": "sha256", + "url": "http://test/url", + "versionCode": "284", + "versionName": "9.6.1.9" + }, + "appId": "101219405", + "appPromotionChannel": "401721412", + "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "cta": "install", + "duration": 6038, + "description": "", + "imageInfo": [ + { + "checkSha256Flag": 0, + "height": 607, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image.jpg", + "width": 1080 + } + ], + "label": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "landingPageType": "3", + "marketAppId": "C101219405", + "title": "%2Ftest%2F" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp" + ] + }, + { + "eventType": "download", + "url": [ + "http://test/download" + ] + }, + { + "eventType": "install", + "url": [ + "http://test/install" + ] + }, + { + "eventType": "downloadstart", + "url": [ + "http://test/downloadstart" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + }, + { + "eventType": "playStart", + "url": [ + "http://test/playStart" + ] + }, + { + "eventType": "playEnd", + "url": [ + "http://test/playEnd" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "u42ohmaufh" + } + ], + "noReportAdTypeEventList": [ + { + "adType": 3, + "eventTypeList": [ + "installFail" + ] + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adomain": [ + "huaweiads" + ], + "crid": "58022259", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":101,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":102,\"img\":{\"type\":3,\"url\":\"http://image.jpg\",\"w\":1080,\"h\":607}},{\"id\":103,\"data\":{\"label\":\"desc\",\"value\":\"\"}},{\"id\":104,\"data\":{\"type\":12,\"value\":\"install\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}", + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 2.8, + "h": 607, + "w": 1080 + }, + "type": "native" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json index d296010cc77..c422f92b23d 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json @@ -91,10 +91,71 @@ "adtype": 3, "slotid": "u42ohmaufh", "detailedCreativeTypeList": [ - "901" + "901", + "905" ], - "h": 200, - "w": 200, + "format": [ + { + "w": 225, + "h": 150 + }, + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 250 + }, + { + "w": 640, + "h": 360 + }, + { + "w": 720, + "h": 1280 + }, + { + "w": 1080, + "h": 170 + }, + { + "w": 1080, + "h": 430 + }, + { + "w": 1080, + "h": 432 + }, + { + "w": 1080, + "h": 504 + }, + { + "w": 1080, + "h": 607 + }, + { + "w": 1080, + "h": 1620 + }, + { + "w": 1200, + "h": 627 + }, + { + "w": 1280, + "h": 720 + }, + { + "w": 1312, + "h": 768 + }, + { + "w": 1920, + "h": 1080 + } + ], "test": 1 } ], diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json index 16313a5588a..49562dacc80 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json @@ -5,7 +5,7 @@ { "id": "test-imp-id", "native": { - "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":102,\"img\":{\"type\":3,\"w\":200,\"h\":200},\"required\":1},{\"id\":103,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"w\":200,\"h\":300,\"wmin\":300,\"hmin\":200},\"required\":1},{\"id\":102,\"img\":{\"type\":3,\"w\":400,\"h\":500,\"wmin\":400,\"hmin\":500},\"required\":1},{\"id\":103,\"img\":{\"type\":3,\"w\":300,\"h\":250,\"wmin\":300,\"hmin\":250},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", "ver": "1.2" }, "ext": { @@ -90,10 +90,23 @@ "adtype": 3, "slotid": "u42ohmaufh", "detailedCreativeTypeList": [ - "904" + "901", + "905" ], - "h": 200, - "w": 200, + "format": [ + { + "w": 200, + "h": 300 + }, + { + "w": 400, + "h": 500 + }, + { + "w": 300, + "h": 250 + } + ], "test": 1 } ], diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json index 6122af732c2..a11f2ca89c8 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json @@ -5,7 +5,7 @@ { "id": "test-imp-id", "native": { - "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":102,\"img\":{\"type\":1,\"w\":20,\"h\":20},\"required\":1},{\"id\":103,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":102,\"img\":{\"type\":1,\"w\":20,\"h\":20},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", "ver": "1.2" }, "ext": { @@ -91,10 +91,71 @@ "adtype": 3, "slotid": "u42ohmaufh", "detailedCreativeTypeList": [ - "904" + "901", + "905" ], - "h": 200, - "w": 200, + "format": [ + { + "w": 225, + "h": 150 + }, + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 250 + }, + { + "w": 640, + "h": 360 + }, + { + "w": 720, + "h": 1280 + }, + { + "w": 1080, + "h": 170 + }, + { + "w": 1080, + "h": 430 + }, + { + "w": 1080, + "h": 432 + }, + { + "w": 1080, + "h": 504 + }, + { + "w": 1080, + "h": 607 + }, + { + "w": 1080, + "h": 1620 + }, + { + "w": 1200, + "h": 627 + }, + { + "w": 1280, + "h": 720 + }, + { + "w": 1312, + "h": 768 + }, + { + "w": 1920, + "h": 1080 + } + ], "test": 1 } ], @@ -320,7 +381,7 @@ "huaweiads" ], "crid": "58022259", - "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":101,\"img\":{\"type\":3,\"url\":\"http://image1.jpg\",\"w\":400,\"h\":350}},{\"id\":102,\"img\":{\"type\":1,\"url\":\"https://icon1.png\",\"w\":160,\"h\":160}},{\"id\":103,\"img\":{\"type\":3,\"url\":\"http://image2.jpg\",\"w\":400,\"h\":300}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"this is a test ad\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":101,\"img\":{\"type\":3,\"url\":\"http://image1.jpg\",\"w\":400,\"h\":350}},{\"id\":102,\"img\":{\"type\":1,\"url\":\"https://icon1.png\",\"w\":160,\"h\":160}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"this is a test ad\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}", "id": "test-imp-id", "impid": "test-imp-id", "price": 2.8, diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json index c38bc9f69b9..20cfb2b82f5 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json @@ -92,10 +92,71 @@ "slotid": "m8x9x3rzff", "test": 1, "detailedCreativeTypeList": [ - "903" + "901", + "905" ], - "h": 200, - "w": 200 + "format": [ + { + "w": 225, + "h": 150 + }, + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 250 + }, + { + "w": 640, + "h": 360 + }, + { + "w": 720, + "h": 1280 + }, + { + "w": 1080, + "h": 170 + }, + { + "w": 1080, + "h": 430 + }, + { + "w": 1080, + "h": 432 + }, + { + "w": 1080, + "h": 504 + }, + { + "w": 1080, + "h": 607 + }, + { + "w": 1080, + "h": 1620 + }, + { + "w": 1200, + "h": 627 + }, + { + "w": 1280, + "h": 720 + }, + { + "w": 1312, + "h": 768 + }, + { + "w": 1920, + "h": 1080 + } + ] } ], "device": { From cad9097fd5731c5042a13747eb5220152e6a7a5d Mon Sep 17 00:00:00 2001 From: Hasan Kanjee <55110940+hasan-kanjee@users.noreply.github.com> Date: Mon, 20 Nov 2023 01:27:55 -0500 Subject: [PATCH 100/138] Flipp: abide by privacy concerns when using flippExt userKey (#3250) --- adapters/flipp/flipp.go | 50 ++++- adapters/flipp/flipp_test.go | 54 ++++++ ...ple-banner-native-param-transmit-eids.json | 181 ++++++++++++++++++ ...simple-banner-native-param-user-coppa.json | 177 +++++++++++++++++ .../simple-banner-native-param-user-gdpr.json | 177 +++++++++++++++++ .../simple-banner-native-param-user.json | 5 + 6 files changed, 640 insertions(+), 4 deletions(-) create mode 100644 adapters/flipp/flipptest/exemplary/simple-banner-native-param-transmit-eids.json create mode 100644 adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-coppa.json create mode 100644 adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-gdpr.json diff --git a/adapters/flipp/flipp.go b/adapters/flipp/flipp.go index c119ec078cc..e758ec741b2 100644 --- a/adapters/flipp/flipp.go +++ b/adapters/flipp/flipp.go @@ -1,6 +1,7 @@ package flipp import ( + "encoding/base64" "encoding/json" "fmt" "net/http" @@ -8,11 +9,13 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/gofrs/uuid" + "github.com/prebid/go-gdpr/consentconstants" + "github.com/prebid/go-gdpr/vendorconsent" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/uuidutil" ) const ( @@ -22,6 +25,8 @@ const ( defaultCurrency = "USD" ) +var uuidGenerator uuidutil.UUIDGenerator + var ( count int64 = 1 adTypes = []int64{4309, 641} @@ -34,6 +39,7 @@ type adapter struct { // Builder builds a new instance of the Flipp adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + uuidGenerator = uuidutil.UUIDRandomGenerator{} bidder := &adapter{ endpoint: config.Endpoint, } @@ -123,14 +129,15 @@ func (a *adapter) processImp(request *openrtb2.BidRequest, imp openrtb2.Imp) (*a var userKey string if request.User != nil && request.User.ID != "" { userKey = request.User.ID - } else if flippExtParams.UserKey != "" { + } else if flippExtParams.UserKey != "" && paramsUserKeyPermitted(request) { userKey = flippExtParams.UserKey } else { - uid, err := uuid.NewV4() + + uid, err := uuidGenerator.Generate() if err != nil { return nil, fmt.Errorf("unable to generate user uuid. %v", err) } - userKey = uid.String() + userKey = uid } keywordsArray := strings.Split(request.Site.Keywords, ",") @@ -223,3 +230,38 @@ func buildBid(decision *InlineModel, impId string) *openrtb2.Bid { } return bid } + +func paramsUserKeyPermitted(request *openrtb2.BidRequest) bool { + if request.Regs != nil { + if request.Regs.COPPA == 1 { + return false + } + if request.Regs.GDPR != nil && *request.Regs.GDPR == 1 { + return false + } + } + if request.Ext != nil { + var extData struct { + TransmitEids *bool `json:"transmitEids,omitempty"` + } + if err := json.Unmarshal(request.Ext, &extData); err == nil { + if extData.TransmitEids != nil && !*extData.TransmitEids { + return false + } + } + } + if request.User != nil && request.User.Consent != "" { + data, err := base64.RawURLEncoding.DecodeString(request.User.Consent) + if err != nil { + return true + } + consent, err := vendorconsent.Parse(data) + if err != nil { + return true + } + if !consent.PurposeAllowed(consentconstants.ContentSelectionDeliveryReporting) { + return false + } + } + return true +} diff --git a/adapters/flipp/flipp_test.go b/adapters/flipp/flipp_test.go index e9ba0d138fd..310b7e145d9 100644 --- a/adapters/flipp/flipp_test.go +++ b/adapters/flipp/flipp_test.go @@ -1,17 +1,29 @@ package flipp import ( + "encoding/json" "testing" + "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/v2/adapters/adapterstest" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" ) +const fakeUuid = "30470a14-2949-4110-abce-b62d57304ad5" + +type TestUUIDGenerator struct{} + +func (TestUUIDGenerator) Generate() (string, error) { + return fakeUuid, nil +} + func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderFlipp, config.Adapter{ Endpoint: "http://example.com/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + uuidGenerator = TestUUIDGenerator{} if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -19,3 +31,45 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "flipptest", bidder) } + +func TestParamsUserKeyPermitted(t *testing.T) { + + t.Run("Coppa is in effect", func(t *testing.T) { + request := &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + COPPA: 1, + }, + } + result := paramsUserKeyPermitted(request) + assert.New(t) + assert.False(t, result, "param user key not permitted because coppa is in effect") + }) + t.Run("The Global Privacy Control is set", func(t *testing.T) { + request := &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GDPR: openrtb2.Int8Ptr(1), + }, + } + result := paramsUserKeyPermitted(request) + assert.New(t) + assert.False(t, result, "param user key not permitted because Global Privacy Control is set") + }) + t.Run("The Prebid transmitEids activity is disallowed", func(t *testing.T) { + extData := struct { + TransmitEids bool `json:"transmitEids"` + }{ + TransmitEids: false, + } + ext, err := json.Marshal(extData) + if err != nil { + t.Fatalf("failed to marshal ext data: %v", err) + } + request := &openrtb2.BidRequest{ + Ext: ext, + } + + result := paramsUserKeyPermitted(request) + assert.New(t) + assert.False(t, result, "param user key not permitted because Prebid transmitEids activity is disallowed") + }) +} diff --git a/adapters/flipp/flipptest/exemplary/simple-banner-native-param-transmit-eids.json b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-transmit-eids.json new file mode 100644 index 00000000000..77ba527b5cf --- /dev/null +++ b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-transmit-eids.json @@ -0,0 +1,181 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 1, + "device": { + "ip": "123.123.123.123" + }, + "site": { + "id": "1243066", + "page": "http://www.example.com/test?flipp-content-code=publisher-test" + }, + "user": { + }, + "ext": { + "transmitEids": false + }, + "regs": { + "coppa": 0, + "gdpr": 0 + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 1800 + } + ] + }, + "ext": { + "bidder": { + "publisherNameIdentifier": "wishabi-test-publisher", + "creativeType": "NativeX", + "siteId": 1243066, + "zoneIds": [285431], + "options": { + "startCompact": true + }, + "userKey": "abc123" + } + } + } + + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/pserver", + "body": { + "ip":"123.123.123.123", + "keywords":[ + "" + ], + "placements":[ + { + "adTypes":[ + 4309, + 641 + ], + "count":1, + "divName":"inline", + "prebid":{ + "creativeType":"NativeX", + "height":1800, + "publisherNameIdentifier":"wishabi-test-publisher", + "requestId":"test-imp-id", + "width":300 + }, + "properties":{ + "contentCode":"publisher-test" + }, + "siteId":1243066, + "zoneIds":[ + 285431 + ], + "options": { + "startCompact": true + } + } + ], + "url":"http://www.example.com/test?flipp-content-code=publisher-test", + "user":{ + "key":"30470a14-2949-4110-abce-b62d57304ad5" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "decisions": { + "inline": [ + { + "adId": 183599115, + "advertiserId": 1988027, + "campaignId": 63285392, + "clickUrl": "https://e-11090.adzerk.net/r?e=eyJ2IjoiMS4xMSIsImF2IjoxOTg4MDI3LCJhdCI6NDMwOSwiYnQiOjAsImNtIjo2MzI4NTM5MiwiY2giOjU4MDgxLCJjayI6e30sImNyIjo4MTMyNTY5MCwiZGkiOiJiOTg3MGNkYTA5MTU0NDlmOTkwZGNkZTNmNjYyNGNhMyIsImRqIjowLCJpaSI6IjJmNjYwMjMyODBmYjQ4NTRiYTY0YzFlYzA1ZDU5MTNiIiwiZG0iOjMsImZjIjoxODM1OTkxMTUsImZsIjoxNzU0MjE3OTUsImlwIjoiMTQyLjE4MS41OC41MiIsIm53IjoxMDkyMiwicGMiOjAsIm9wIjowLCJlYyI6MCwiZ20iOjAsImVwIjpudWxsLCJwciI6MjMyNjk5LCJydCI6MywicnMiOjUwMCwic2EiOiIzNCIsInNiIjoiaS0wNDZjMWNlNWRjYmExMTVjNSIsInNwIjozNzIzMDU1LCJzdCI6MTI0MzA2NiwidWsiOiJkOTU1N2Q2NS1kNWI5LTQyOTItYjg2My0xNGEyOTcyNTk3ZjQiLCJ6biI6Mjg1NDMxLCJ0cyI6MTY4MDU1NTc4MzkyMiwicG4iOiJpbmxpbmUiLCJnYyI6dHJ1ZSwiZ0MiOnRydWUsImdzIjoibm9uZSIsInR6IjoiQW1lcmljYS9OZXdfWW9yayIsInVyIjoiaHR0cDovL3d3dy5mbGlwcC5jb20ifQ&s=Mnss8P1kc37Eftik5RJvLJb4S9Y", + "contents": [ + { + "data": { + "customData": { + "campaignConfigUrl": "https://campaign-config.flipp.com/dist-campaign-admin/215", + "campaignNameIdentifier": "US Grocery Demo (Kroger)" + }, + "height": 1800, + "width": 300 + }, + "type": "raw" + } + ], + "creativeId": 81325690, + "flightId": 175421795, + "height": 1800, + "impressionUrl": "https://e-11090.adzerk.net/i.gif?e=eyJ2IjoiMS4xMSIsImF2IjoxOTg4MDI3LCJhdCI6NDMwOSwiYnQiOjAsImNtIjo2MzI4NTM5MiwiY2giOjU4MDgxLCJjayI6e30sImNyIjo4MTMyNTY5MCwiZGkiOiJiOTg3MGNkYTA5MTU0NDlmOTkwZGNkZTNmNjYyNGNhMyIsImRqIjowLCJpaSI6IjJmNjYwMjMyODBmYjQ4NTRiYTY0YzFlYzA1ZDU5MTNiIiwiZG0iOjMsImZjIjoxODM1OTkxMTUsImZsIjoxNzU0MjE3OTUsImlwIjoiMTQyLjE4MS41OC41MiIsIm53IjoxMDkyMiwicGMiOjAsIm9wIjowLCJlYyI6MCwiZ20iOjAsImVwIjpudWxsLCJwciI6MjMyNjk5LCJydCI6MywicnMiOjUwMCwic2EiOiIzNCIsInNiIjoiaS0wNDZjMWNlNWRjYmExMTVjNSIsInNwIjozNzIzMDU1LCJzdCI6MTI0MzA2NiwidWsiOiJkOTU1N2Q2NS1kNWI5LTQyOTItYjg2My0xNGEyOTcyNTk3ZjQiLCJ6biI6Mjg1NDMxLCJ0cyI6MTY4MDU1NTc4MzkyMywicG4iOiJpbmxpbmUiLCJnYyI6dHJ1ZSwiZ0MiOnRydWUsImdzIjoibm9uZSIsInR6IjoiQW1lcmljYS9OZXdfWW9yayIsImJhIjoxLCJmcSI6MH0&s=Qce4_IohtESeNA_sB71Qjb4TouY", + "prebid": { + "cpm": 12.34, + "creative":"creativeContent", + "creativeType": "NativeX", + "requestId": "test-imp-id" + }, + "priorityId": 232699, + "storefront": { + "campaignConfig": { + "fallbackImageUrl": "https://f.wishabi.net/arbitrary_files/115856/1666018811/115856_Featured Local Savings - Flipp Fallback - US (1).png", + "fallbackLinkUrl": "https://flipp.com/flyers/", + "tags": { + "advertiser": { + "engagement": "https://f.wishabi.net/creative/happyfruits/_itemlevelv2/apple.png?cb=[[random]]" + } + } + }, + "flyer_id": 5554080, + "flyer_run_id": 873256, + "flyer_type_id": 2826, + "is_fallback": true, + "merchant": "Kroger", + "merchant_id": 2778, + "name": "Weekly Ad", + "storefront_logo_url": "https://images.wishabi.net/merchants/qX1/BGIzc9sFcA==/RackMultipart20210421-1-e3k5rx.jpeg", + "storefront_payload_url": "https://cdn-gateflipp.flippback.com/storefront-payload/v2/873256/5554080/ff14f675705934507c269b5750e124a323bc9bf60e8a6f210f422f4528b233ff?merchant_id=2778", + "valid_from": "2023-03-29 04:00:00 +0000 UTC", + "valid_to": "2023-04-05 03:59:59 +0000 UTC" + }, + "width": 300 + } + ] + }, + "location": { + "accuracy_radius": 5, + "city": "Toronto", + "country": "CA", + "ip": "123.123.123.123", + "postal_code": "M4S", + "region": "ON", + "region_name": "Ontario" + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "183599115", + "impid": "test-imp-id", + "price": 12.34, + "adm": "creativeContent", + "crid": "81325690", + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-coppa.json b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-coppa.json new file mode 100644 index 00000000000..f61f6d9d710 --- /dev/null +++ b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-coppa.json @@ -0,0 +1,177 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 1, + "device": { + "ip": "123.123.123.123" + }, + "site": { + "id": "1243066", + "page": "http://www.example.com/test?flipp-content-code=publisher-test" + }, + "regs": { + "coppa": 1 + }, + "user": { + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 1800 + } + ] + }, + "ext": { + "bidder": { + "publisherNameIdentifier": "wishabi-test-publisher", + "creativeType": "NativeX", + "siteId": 1243066, + "zoneIds": [285431], + "options": { + "startCompact": true + }, + "userKey": "abc123" + } + } + } + + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/pserver", + "body": { + "ip":"123.123.123.123", + "keywords":[ + "" + ], + "placements":[ + { + "adTypes":[ + 4309, + 641 + ], + "count":1, + "divName":"inline", + "prebid":{ + "creativeType":"NativeX", + "height":1800, + "publisherNameIdentifier":"wishabi-test-publisher", + "requestId":"test-imp-id", + "width":300 + }, + "properties":{ + "contentCode":"publisher-test" + }, + "siteId":1243066, + "zoneIds":[ + 285431 + ], + "options": { + "startCompact": true + } + } + ], + "url":"http://www.example.com/test?flipp-content-code=publisher-test", + "user":{ + "key":"30470a14-2949-4110-abce-b62d57304ad5" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "decisions": { + "inline": [ + { + "adId": 183599115, + "advertiserId": 1988027, + "campaignId": 63285392, + "clickUrl": "https://e-11090.adzerk.net/r?e=eyJ2IjoiMS4xMSIsImF2IjoxOTg4MDI3LCJhdCI6NDMwOSwiYnQiOjAsImNtIjo2MzI4NTM5MiwiY2giOjU4MDgxLCJjayI6e30sImNyIjo4MTMyNTY5MCwiZGkiOiJiOTg3MGNkYTA5MTU0NDlmOTkwZGNkZTNmNjYyNGNhMyIsImRqIjowLCJpaSI6IjJmNjYwMjMyODBmYjQ4NTRiYTY0YzFlYzA1ZDU5MTNiIiwiZG0iOjMsImZjIjoxODM1OTkxMTUsImZsIjoxNzU0MjE3OTUsImlwIjoiMTQyLjE4MS41OC41MiIsIm53IjoxMDkyMiwicGMiOjAsIm9wIjowLCJlYyI6MCwiZ20iOjAsImVwIjpudWxsLCJwciI6MjMyNjk5LCJydCI6MywicnMiOjUwMCwic2EiOiIzNCIsInNiIjoiaS0wNDZjMWNlNWRjYmExMTVjNSIsInNwIjozNzIzMDU1LCJzdCI6MTI0MzA2NiwidWsiOiJkOTU1N2Q2NS1kNWI5LTQyOTItYjg2My0xNGEyOTcyNTk3ZjQiLCJ6biI6Mjg1NDMxLCJ0cyI6MTY4MDU1NTc4MzkyMiwicG4iOiJpbmxpbmUiLCJnYyI6dHJ1ZSwiZ0MiOnRydWUsImdzIjoibm9uZSIsInR6IjoiQW1lcmljYS9OZXdfWW9yayIsInVyIjoiaHR0cDovL3d3dy5mbGlwcC5jb20ifQ&s=Mnss8P1kc37Eftik5RJvLJb4S9Y", + "contents": [ + { + "data": { + "customData": { + "campaignConfigUrl": "https://campaign-config.flipp.com/dist-campaign-admin/215", + "campaignNameIdentifier": "US Grocery Demo (Kroger)" + }, + "height": 1800, + "width": 300 + }, + "type": "raw" + } + ], + "creativeId": 81325690, + "flightId": 175421795, + "height": 1800, + "impressionUrl": "https://e-11090.adzerk.net/i.gif?e=eyJ2IjoiMS4xMSIsImF2IjoxOTg4MDI3LCJhdCI6NDMwOSwiYnQiOjAsImNtIjo2MzI4NTM5MiwiY2giOjU4MDgxLCJjayI6e30sImNyIjo4MTMyNTY5MCwiZGkiOiJiOTg3MGNkYTA5MTU0NDlmOTkwZGNkZTNmNjYyNGNhMyIsImRqIjowLCJpaSI6IjJmNjYwMjMyODBmYjQ4NTRiYTY0YzFlYzA1ZDU5MTNiIiwiZG0iOjMsImZjIjoxODM1OTkxMTUsImZsIjoxNzU0MjE3OTUsImlwIjoiMTQyLjE4MS41OC41MiIsIm53IjoxMDkyMiwicGMiOjAsIm9wIjowLCJlYyI6MCwiZ20iOjAsImVwIjpudWxsLCJwciI6MjMyNjk5LCJydCI6MywicnMiOjUwMCwic2EiOiIzNCIsInNiIjoiaS0wNDZjMWNlNWRjYmExMTVjNSIsInNwIjozNzIzMDU1LCJzdCI6MTI0MzA2NiwidWsiOiJkOTU1N2Q2NS1kNWI5LTQyOTItYjg2My0xNGEyOTcyNTk3ZjQiLCJ6biI6Mjg1NDMxLCJ0cyI6MTY4MDU1NTc4MzkyMywicG4iOiJpbmxpbmUiLCJnYyI6dHJ1ZSwiZ0MiOnRydWUsImdzIjoibm9uZSIsInR6IjoiQW1lcmljYS9OZXdfWW9yayIsImJhIjoxLCJmcSI6MH0&s=Qce4_IohtESeNA_sB71Qjb4TouY", + "prebid": { + "cpm": 12.34, + "creative":"creativeContent", + "creativeType": "NativeX", + "requestId": "test-imp-id" + }, + "priorityId": 232699, + "storefront": { + "campaignConfig": { + "fallbackImageUrl": "https://f.wishabi.net/arbitrary_files/115856/1666018811/115856_Featured Local Savings - Flipp Fallback - US (1).png", + "fallbackLinkUrl": "https://flipp.com/flyers/", + "tags": { + "advertiser": { + "engagement": "https://f.wishabi.net/creative/happyfruits/_itemlevelv2/apple.png?cb=[[random]]" + } + } + }, + "flyer_id": 5554080, + "flyer_run_id": 873256, + "flyer_type_id": 2826, + "is_fallback": true, + "merchant": "Kroger", + "merchant_id": 2778, + "name": "Weekly Ad", + "storefront_logo_url": "https://images.wishabi.net/merchants/qX1/BGIzc9sFcA==/RackMultipart20210421-1-e3k5rx.jpeg", + "storefront_payload_url": "https://cdn-gateflipp.flippback.com/storefront-payload/v2/873256/5554080/ff14f675705934507c269b5750e124a323bc9bf60e8a6f210f422f4528b233ff?merchant_id=2778", + "valid_from": "2023-03-29 04:00:00 +0000 UTC", + "valid_to": "2023-04-05 03:59:59 +0000 UTC" + }, + "width": 300 + } + ] + }, + "location": { + "accuracy_radius": 5, + "city": "Toronto", + "country": "CA", + "ip": "123.123.123.123", + "postal_code": "M4S", + "region": "ON", + "region_name": "Ontario" + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "183599115", + "impid": "test-imp-id", + "price": 12.34, + "adm": "creativeContent", + "crid": "81325690", + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-gdpr.json b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-gdpr.json new file mode 100644 index 00000000000..446ecd7d3f1 --- /dev/null +++ b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-gdpr.json @@ -0,0 +1,177 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 1, + "device": { + "ip": "123.123.123.123" + }, + "site": { + "id": "1243066", + "page": "http://www.example.com/test?flipp-content-code=publisher-test" + }, + "user": { + }, + "regs": { + "gdpr": 1 + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 1800 + } + ] + }, + "ext": { + "bidder": { + "publisherNameIdentifier": "wishabi-test-publisher", + "creativeType": "NativeX", + "siteId": 1243066, + "zoneIds": [285431], + "options": { + "startCompact": true + }, + "userKey": "abc123" + } + } + } + + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/pserver", + "body": { + "ip":"123.123.123.123", + "keywords":[ + "" + ], + "placements":[ + { + "adTypes":[ + 4309, + 641 + ], + "count":1, + "divName":"inline", + "prebid":{ + "creativeType":"NativeX", + "height":1800, + "publisherNameIdentifier":"wishabi-test-publisher", + "requestId":"test-imp-id", + "width":300 + }, + "properties":{ + "contentCode":"publisher-test" + }, + "siteId":1243066, + "zoneIds":[ + 285431 + ], + "options": { + "startCompact": true + } + } + ], + "url":"http://www.example.com/test?flipp-content-code=publisher-test", + "user":{ + "key":"30470a14-2949-4110-abce-b62d57304ad5" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "decisions": { + "inline": [ + { + "adId": 183599115, + "advertiserId": 1988027, + "campaignId": 63285392, + "clickUrl": "https://e-11090.adzerk.net/r?e=eyJ2IjoiMS4xMSIsImF2IjoxOTg4MDI3LCJhdCI6NDMwOSwiYnQiOjAsImNtIjo2MzI4NTM5MiwiY2giOjU4MDgxLCJjayI6e30sImNyIjo4MTMyNTY5MCwiZGkiOiJiOTg3MGNkYTA5MTU0NDlmOTkwZGNkZTNmNjYyNGNhMyIsImRqIjowLCJpaSI6IjJmNjYwMjMyODBmYjQ4NTRiYTY0YzFlYzA1ZDU5MTNiIiwiZG0iOjMsImZjIjoxODM1OTkxMTUsImZsIjoxNzU0MjE3OTUsImlwIjoiMTQyLjE4MS41OC41MiIsIm53IjoxMDkyMiwicGMiOjAsIm9wIjowLCJlYyI6MCwiZ20iOjAsImVwIjpudWxsLCJwciI6MjMyNjk5LCJydCI6MywicnMiOjUwMCwic2EiOiIzNCIsInNiIjoiaS0wNDZjMWNlNWRjYmExMTVjNSIsInNwIjozNzIzMDU1LCJzdCI6MTI0MzA2NiwidWsiOiJkOTU1N2Q2NS1kNWI5LTQyOTItYjg2My0xNGEyOTcyNTk3ZjQiLCJ6biI6Mjg1NDMxLCJ0cyI6MTY4MDU1NTc4MzkyMiwicG4iOiJpbmxpbmUiLCJnYyI6dHJ1ZSwiZ0MiOnRydWUsImdzIjoibm9uZSIsInR6IjoiQW1lcmljYS9OZXdfWW9yayIsInVyIjoiaHR0cDovL3d3dy5mbGlwcC5jb20ifQ&s=Mnss8P1kc37Eftik5RJvLJb4S9Y", + "contents": [ + { + "data": { + "customData": { + "campaignConfigUrl": "https://campaign-config.flipp.com/dist-campaign-admin/215", + "campaignNameIdentifier": "US Grocery Demo (Kroger)" + }, + "height": 1800, + "width": 300 + }, + "type": "raw" + } + ], + "creativeId": 81325690, + "flightId": 175421795, + "height": 1800, + "impressionUrl": "https://e-11090.adzerk.net/i.gif?e=eyJ2IjoiMS4xMSIsImF2IjoxOTg4MDI3LCJhdCI6NDMwOSwiYnQiOjAsImNtIjo2MzI4NTM5MiwiY2giOjU4MDgxLCJjayI6e30sImNyIjo4MTMyNTY5MCwiZGkiOiJiOTg3MGNkYTA5MTU0NDlmOTkwZGNkZTNmNjYyNGNhMyIsImRqIjowLCJpaSI6IjJmNjYwMjMyODBmYjQ4NTRiYTY0YzFlYzA1ZDU5MTNiIiwiZG0iOjMsImZjIjoxODM1OTkxMTUsImZsIjoxNzU0MjE3OTUsImlwIjoiMTQyLjE4MS41OC41MiIsIm53IjoxMDkyMiwicGMiOjAsIm9wIjowLCJlYyI6MCwiZ20iOjAsImVwIjpudWxsLCJwciI6MjMyNjk5LCJydCI6MywicnMiOjUwMCwic2EiOiIzNCIsInNiIjoiaS0wNDZjMWNlNWRjYmExMTVjNSIsInNwIjozNzIzMDU1LCJzdCI6MTI0MzA2NiwidWsiOiJkOTU1N2Q2NS1kNWI5LTQyOTItYjg2My0xNGEyOTcyNTk3ZjQiLCJ6biI6Mjg1NDMxLCJ0cyI6MTY4MDU1NTc4MzkyMywicG4iOiJpbmxpbmUiLCJnYyI6dHJ1ZSwiZ0MiOnRydWUsImdzIjoibm9uZSIsInR6IjoiQW1lcmljYS9OZXdfWW9yayIsImJhIjoxLCJmcSI6MH0&s=Qce4_IohtESeNA_sB71Qjb4TouY", + "prebid": { + "cpm": 12.34, + "creative":"creativeContent", + "creativeType": "NativeX", + "requestId": "test-imp-id" + }, + "priorityId": 232699, + "storefront": { + "campaignConfig": { + "fallbackImageUrl": "https://f.wishabi.net/arbitrary_files/115856/1666018811/115856_Featured Local Savings - Flipp Fallback - US (1).png", + "fallbackLinkUrl": "https://flipp.com/flyers/", + "tags": { + "advertiser": { + "engagement": "https://f.wishabi.net/creative/happyfruits/_itemlevelv2/apple.png?cb=[[random]]" + } + } + }, + "flyer_id": 5554080, + "flyer_run_id": 873256, + "flyer_type_id": 2826, + "is_fallback": true, + "merchant": "Kroger", + "merchant_id": 2778, + "name": "Weekly Ad", + "storefront_logo_url": "https://images.wishabi.net/merchants/qX1/BGIzc9sFcA==/RackMultipart20210421-1-e3k5rx.jpeg", + "storefront_payload_url": "https://cdn-gateflipp.flippback.com/storefront-payload/v2/873256/5554080/ff14f675705934507c269b5750e124a323bc9bf60e8a6f210f422f4528b233ff?merchant_id=2778", + "valid_from": "2023-03-29 04:00:00 +0000 UTC", + "valid_to": "2023-04-05 03:59:59 +0000 UTC" + }, + "width": 300 + } + ] + }, + "location": { + "accuracy_radius": 5, + "city": "Toronto", + "country": "CA", + "ip": "123.123.123.123", + "postal_code": "M4S", + "region": "ON", + "region_name": "Ontario" + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "183599115", + "impid": "test-imp-id", + "price": 12.34, + "adm": "creativeContent", + "crid": "81325690", + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user.json b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user.json index 272bcd744ab..21957ae7096 100644 --- a/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user.json +++ b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user.json @@ -10,6 +10,11 @@ "page": "http://www.example.com/test?flipp-content-code=publisher-test" }, "user": { + "consent": "CP0xeHPP0xeHPBcABBENAgCAAPAAAAAAAGcgAAAAAAAA" + }, + "regs": { + "coppa": 0, + "gdpr": 0 }, "imp": [ { From 01f3320e4ea1a6c794418817ae80ad9d107835a6 Mon Sep 17 00:00:00 2001 From: Simple Magic Software Date: Mon, 20 Nov 2023 02:30:36 -0500 Subject: [PATCH 101/138] New Adapter: oms (#3264) Co-authored-by: yfcr --- adapters/oms/oms.go | 80 +++++++ adapters/oms/oms_test.go | 20 ++ .../exemplary/simple-banner-cookie-uid.json | 128 +++++++++++ .../simple-banner-multiple-bids.json | 216 ++++++++++++++++++ .../omstest/exemplary/simple-banner-uid.json | 149 ++++++++++++ .../exemplary/simple-multi-type-banner.json | 161 +++++++++++++ .../204-response-from-target.json | 85 +++++++ .../400-response-from-target.json | 90 ++++++++ .../500-response-from-target.json | 90 ++++++++ .../supplemental/simple-banner-with-ipv6.json | 142 ++++++++++++ adapters/oms/params_test.go | 56 +++++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_oms.go | 6 + static/bidder-info/oms.yaml | 11 + static/bidder-params/oms.json | 14 ++ 16 files changed, 1252 insertions(+) create mode 100644 adapters/oms/oms.go create mode 100644 adapters/oms/oms_test.go create mode 100755 adapters/oms/omstest/exemplary/simple-banner-cookie-uid.json create mode 100644 adapters/oms/omstest/exemplary/simple-banner-multiple-bids.json create mode 100755 adapters/oms/omstest/exemplary/simple-banner-uid.json create mode 100644 adapters/oms/omstest/exemplary/simple-multi-type-banner.json create mode 100755 adapters/oms/omstest/supplemental/204-response-from-target.json create mode 100755 adapters/oms/omstest/supplemental/400-response-from-target.json create mode 100755 adapters/oms/omstest/supplemental/500-response-from-target.json create mode 100755 adapters/oms/omstest/supplemental/simple-banner-with-ipv6.json create mode 100644 adapters/oms/params_test.go create mode 100644 openrtb_ext/imp_oms.go create mode 100644 static/bidder-info/oms.yaml create mode 100644 static/bidder-params/oms.json diff --git a/adapters/oms/oms.go b/adapters/oms/oms.go new file mode 100644 index 00000000000..af16cc78ace --- /dev/null +++ b/adapters/oms/oms.go @@ -0,0 +1,80 @@ +package oms + +import ( + "encoding/json" + "fmt" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "net/http" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the OMS adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode == http.StatusBadRequest { + err := &errortypes.BadInput{ + Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + if len(response.Cur) == 0 { + bidResponse.Currency = response.Cur + } + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: openrtb_ext.BidTypeBanner, + }) + } + } + + return bidResponse, nil +} diff --git a/adapters/oms/oms_test.go b/adapters/oms/oms_test.go new file mode 100644 index 00000000000..9c3c4119f10 --- /dev/null +++ b/adapters/oms/oms_test.go @@ -0,0 +1,20 @@ +package oms + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderOms, config.Adapter{ + Endpoint: "http://rt.marphezis.com/pbs"}, config.Server{ExternalUrl: "http://hosturl.com"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "omstest", bidder) +} diff --git a/adapters/oms/omstest/exemplary/simple-banner-cookie-uid.json b/adapters/oms/omstest/exemplary/simple-banner-cookie-uid.json new file mode 100755 index 00000000000..576173b548c --- /dev/null +++ b/adapters/oms/omstest/exemplary/simple-banner-cookie-uid.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "buyeruid": "oms-user-id" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "buyeruid": "oms-user-id" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/oms/omstest/exemplary/simple-banner-multiple-bids.json b/adapters/oms/omstest/exemplary/simple-banner-multiple-bids.json new file mode 100644 index 00000000000..6b292b27a53 --- /dev/null +++ b/adapters/oms/omstest/exemplary/simple-banner-multiple-bids.json @@ -0,0 +1,216 @@ +{ + "mockBidRequest": { + "id": "test-request-id-multiple-bids", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + }, + { + "id": "test-imp-id2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id-multiple-bids", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + }, + { + "id": "test-imp-id2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + }, + { + "bid": [ + { + "id": "test-slot-id2", + "impid": "test-imp-id2", + "price": 0.5, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id2", + "impid": "test-imp-id2", + "price": 0.5, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/oms/omstest/exemplary/simple-banner-uid.json b/adapters/oms/omstest/exemplary/simple-banner-uid.json new file mode 100755 index 00000000000..077c5562448 --- /dev/null +++ b/adapters/oms/omstest/exemplary/simple-banner-uid.json @@ -0,0 +1,149 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/oms/omstest/exemplary/simple-multi-type-banner.json b/adapters/oms/omstest/exemplary/simple-multi-type-banner.json new file mode 100644 index 00000000000..7e96dbfcc7e --- /dev/null +++ b/adapters/oms/omstest/exemplary/simple-multi-type-banner.json @@ -0,0 +1,161 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/oms/omstest/supplemental/204-response-from-target.json b/adapters/oms/omstest/supplemental/204-response-from-target.json new file mode 100755 index 00000000000..63a0829d9f3 --- /dev/null +++ b/adapters/oms/omstest/supplemental/204-response-from-target.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedMakeBidsErrors": [] + } + \ No newline at end of file diff --git a/adapters/oms/omstest/supplemental/400-response-from-target.json b/adapters/oms/omstest/supplemental/400-response-from-target.json new file mode 100755 index 00000000000..831788f229d --- /dev/null +++ b/adapters/oms/omstest/supplemental/400-response-from-target.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/oms/omstest/supplemental/500-response-from-target.json b/adapters/oms/omstest/supplemental/500-response-from-target.json new file mode 100755 index 00000000000..9e4e48e2f2b --- /dev/null +++ b/adapters/oms/omstest/supplemental/500-response-from-target.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/oms/omstest/supplemental/simple-banner-with-ipv6.json b/adapters/oms/omstest/supplemental/simple-banner-with-ipv6.json new file mode 100755 index 00000000000..f473ecd6f21 --- /dev/null +++ b/adapters/oms/omstest/supplemental/simple-banner-with-ipv6.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/oms/params_test.go b/adapters/oms/params_test.go new file mode 100644 index 00000000000..daa78fe7ddf --- /dev/null +++ b/adapters/oms/params_test.go @@ -0,0 +1,56 @@ +package oms + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/oms.json +// +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.oms + +// TestValidParams makes sure that the oms schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderOms, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected oms params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the oms schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderOms, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"pid": "12345"}`, + `{"pid": "123456"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"pid": "0"}`, +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index b859af01148..ca3fa778f0d 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -125,6 +125,7 @@ import ( "github.com/prebid/prebid-server/v2/adapters/motorik" "github.com/prebid/prebid-server/v2/adapters/nextmillennium" "github.com/prebid/prebid-server/v2/adapters/nobid" + "github.com/prebid/prebid-server/v2/adapters/oms" "github.com/prebid/prebid-server/v2/adapters/onetag" "github.com/prebid/prebid-server/v2/adapters/openweb" "github.com/prebid/prebid-server/v2/adapters/openx" @@ -322,6 +323,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderMotorik: motorik.Builder, openrtb_ext.BidderNextMillennium: nextmillennium.Builder, openrtb_ext.BidderNoBid: nobid.Builder, + openrtb_ext.BidderOms: oms.Builder, openrtb_ext.BidderOneTag: onetag.Builder, openrtb_ext.BidderOpenWeb: openweb.Builder, openrtb_ext.BidderOpenx: openx.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index a921f981aee..172d030ba31 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -144,6 +144,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderMotorik, BidderNextMillennium, BidderNoBid, + BidderOms, BidderOneTag, BidderOpenWeb, BidderOpenx, @@ -423,6 +424,7 @@ const ( BidderMotorik BidderName = "motorik" BidderNextMillennium BidderName = "nextmillennium" BidderNoBid BidderName = "nobid" + BidderOms BidderName = "oms" BidderOneTag BidderName = "onetag" BidderOpenWeb BidderName = "openweb" BidderOpenx BidderName = "openx" diff --git a/openrtb_ext/imp_oms.go b/openrtb_ext/imp_oms.go new file mode 100644 index 00000000000..4e12da86264 --- /dev/null +++ b/openrtb_ext/imp_oms.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpOms defines the contract for bidrequest.imp[i].ext.prebid.bidder.oms +type ExtImpOms struct { + PublisherID string `json:"pid"` +} diff --git a/static/bidder-info/oms.yaml b/static/bidder-info/oms.yaml new file mode 100644 index 00000000000..8bb9299d6e9 --- /dev/null +++ b/static/bidder-info/oms.yaml @@ -0,0 +1,11 @@ +endpoint: "http://rt.marphezis.com/pbs" +maintainer: + email: "prebid@onlinemediasolutions.com" +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner + diff --git a/static/bidder-params/oms.json b/static/bidder-params/oms.json new file mode 100644 index 00000000000..f33286d10d9 --- /dev/null +++ b/static/bidder-params/oms.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Online Media Solutions Adapter Params", + "description": "A schema which validates params accepted by the OMS adapter", + "type": "object", + "properties": { + "pid": { + "type": "string", + "description": "An id used to identify OMS publisher.", + "minLength": 5 + } + }, + "required": ["pid"] +} From 94d86825acf52c25ea9ca648d9a4fc5b09e72c03 Mon Sep 17 00:00:00 2001 From: Witek Galecki <49022890+wgalecki@users.noreply.github.com> Date: Mon, 20 Nov 2023 09:07:54 +0100 Subject: [PATCH 102/138] adquery: added device ip and ua to bidder request (#1) (#3288) --- adapters/adquery/adquery.go | 33 ++++- adapters/adquery/adquery_test.go | 2 +- .../adquery/adquerytest/exemplary/empty.json | 9 +- .../adquerytest/exemplary/many-imps.json | 55 +++++--- .../adquerytest/exemplary/no-currency.json | 37 +++-- .../adquery/adquerytest/exemplary/ok.json | 38 +++-- .../exemplary/single-imp-banner-format.json | 29 ++-- .../adquerytest/supplemental/data-null.json | 15 +- .../invalid-numerical-values.json | 51 +++---- .../supplemental/malformed-resp.json | 37 ++--- .../supplemental/mediatype-unknown.json | 37 ++--- .../supplemental/mediatype-video.json | 37 ++--- .../adquerytest/supplemental/no-device.json | 128 +++++++++++++++++ .../supplemental/no-imp-banner-measures.json | 15 +- .../supplemental/no-imp-banner.json | 15 +- .../adquerytest/supplemental/no-site.json | 130 ++++++++++++++++++ .../supplemental/resp-bad-request.json | 33 +++-- .../supplemental/resp-no-content.json | 26 ++-- .../supplemental/resp-server-error.json | 33 +++-- adapters/adquery/types.go | 3 + static/bidder-info/adquery.yaml | 13 +- 21 files changed, 570 insertions(+), 206 deletions(-) create mode 100644 adapters/adquery/adquerytest/supplemental/no-device.json create mode 100644 adapters/adquery/adquerytest/supplemental/no-site.json diff --git a/adapters/adquery/adquery.go b/adapters/adquery/adquery.go index 92a1733dae6..a1e8f638d7f 100644 --- a/adapters/adquery/adquery.go +++ b/adapters/adquery/adquery.go @@ -34,10 +34,7 @@ func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) ( } func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - headers.Add("x-openrtb-version", "2.5") + headers := buildHeaders(request) var result []*adapters.RequestData var errs []error @@ -112,13 +109,27 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData return bidResponse, nil } +func buildHeaders(bidReq *openrtb2.BidRequest) http.Header { + headers := http.Header{} + + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + if bidReq.Device != nil && len(bidReq.Device.IP) > 0 { + headers.Add("X-Forwarded-For", bidReq.Device.IP) + } + + return headers +} + func buildRequest(bidReq *openrtb2.BidRequest, imp *openrtb2.Imp, ext *openrtb_ext.ImpExtAdQuery) *BidderRequest { userId := "" if bidReq.User != nil { userId = bidReq.User.ID } - return &BidderRequest{ + bidderRequest := &BidderRequest{ V: prebidVersion, PlacementCode: ext.PlacementID, AuctionId: "", @@ -132,6 +143,18 @@ func buildRequest(bidReq *openrtb2.BidRequest, imp *openrtb2.Imp, ext *openrtb_e BidderRequestsCount: 1, Sizes: getImpSizes(imp), } + + if bidReq.Device != nil { + bidderRequest.BidIp = bidReq.Device.IP + bidderRequest.BidIpv6 = bidReq.Device.IPv6 + bidderRequest.BidUa = bidReq.Device.UA + } + + if bidReq.Site != nil { + bidderRequest.BidPageUrl = bidReq.Site.Page + } + + return bidderRequest } func parseExt(ext json.RawMessage) (*openrtb_ext.ImpExtAdQuery, error) { diff --git a/adapters/adquery/adquery_test.go b/adapters/adquery/adquery_test.go index 2b88c3b82c7..792e7c553a8 100644 --- a/adapters/adquery/adquery_test.go +++ b/adapters/adquery/adquery_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdquery, config.Adapter{ - Endpoint: "https://bidder.adquery.io/prebid/bid"}, + Endpoint: "https://bidder2.adquery.io/prebid/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 902, DataCenter: "2"}) if buildErr != nil { diff --git a/adapters/adquery/adquerytest/exemplary/empty.json b/adapters/adquery/adquerytest/exemplary/empty.json index 04f1d21bcab..055d9829e63 100644 --- a/adapters/adquery/adquerytest/exemplary/empty.json +++ b/adapters/adquery/adquerytest/exemplary/empty.json @@ -4,9 +4,16 @@ "user": { "id": "xyz" }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, "imp": [], "bidder": "adquery" }, "httpCalls": [], "expectedBidResponses": [] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/exemplary/many-imps.json b/adapters/adquery/adquerytest/exemplary/many-imps.json index a3985c87dc1..b3f5f8c9a78 100644 --- a/adapters/adquery/adquerytest/exemplary/many-imps.json +++ b/adapters/adquery/adquerytest/exemplary/many-imps.json @@ -4,6 +4,13 @@ "user": { "id": "d93f2a0e5f0fe2cc3a6e" }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, "imp": [ { "id": "1", @@ -27,7 +34,8 @@ "type": "banner" } } - },{ + }, + { "id": "2", "tagid": "test-banner-imp-id-2", "bidder": "adquery", @@ -52,21 +60,25 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], - "X-Openrtb-Version": ["2.5"] + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", @@ -98,9 +110,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "EUR", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -117,23 +127,28 @@ } } } - },{ + }, + { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], - "X-Openrtb-Version": ["2.5"] + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] }, "body": { "adUnitCode": "test-banner-imp-id-2", - "bidId": "22e26bd9a702bc2", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc2", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f898", "sizes": "300x250", "type": "banner", @@ -165,9 +180,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "EUR", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -224,4 +237,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/exemplary/no-currency.json b/adapters/adquery/adquerytest/exemplary/no-currency.json index e97e4b9beaa..301eec783fa 100644 --- a/adapters/adquery/adquerytest/exemplary/no-currency.json +++ b/adapters/adquery/adquerytest/exemplary/no-currency.json @@ -4,6 +4,13 @@ "user": { "id": "d93f2a0e5f0fe2cc3a6e" }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, "imp": [ { "id": "1", @@ -22,10 +29,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,21 +41,25 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], - "X-Openrtb-Version": ["2.5"] + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", @@ -79,9 +90,7 @@ "domain": "https://bidder.adquery.io", "urlAdq": "https://adquery.io", "creationId": 7211, - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -120,4 +129,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/exemplary/ok.json b/adapters/adquery/adquerytest/exemplary/ok.json index e725e055293..573fb2336e6 100644 --- a/adapters/adquery/adquerytest/exemplary/ok.json +++ b/adapters/adquery/adquerytest/exemplary/ok.json @@ -4,6 +4,14 @@ "user": { "id": "d93f2a0e5f0fe2cc3a6e" }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ipv6": "2001:4860:4860::8888", + "ua": "PostmanRuntime/7.26.8" + }, "imp": [ { "id": "1", @@ -22,10 +30,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,21 +42,25 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], - "X-Openrtb-Version": ["2.5"] + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "2001:4860:4860::8888", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", @@ -80,9 +92,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "EUR", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -121,4 +131,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/exemplary/single-imp-banner-format.json b/adapters/adquery/adquerytest/exemplary/single-imp-banner-format.json index 7a0092d5f5a..5b2a54a70d3 100644 --- a/adapters/adquery/adquerytest/exemplary/single-imp-banner-format.json +++ b/adapters/adquery/adquerytest/exemplary/single-imp-banner-format.json @@ -4,6 +4,13 @@ "user": { "id": "d93f2a0e5f0fe2cc3a6e" }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, "imp": [ { "id": "1", @@ -26,21 +33,25 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], - "X-Openrtb-Version": ["2.5"] + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100", "type": "banner", @@ -72,9 +83,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "PLN", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -112,4 +121,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/supplemental/data-null.json b/adapters/adquery/adquerytest/supplemental/data-null.json index 0d21a2bd9f7..8bbe8dc501f 100644 --- a/adapters/adquery/adquerytest/supplemental/data-null.json +++ b/adapters/adquery/adquerytest/supplemental/data-null.json @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,13 +42,16 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", @@ -69,4 +72,4 @@ "bids": [] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/supplemental/invalid-numerical-values.json b/adapters/adquery/adquerytest/supplemental/invalid-numerical-values.json index daff3d0828c..a9664e5232e 100644 --- a/adapters/adquery/adquerytest/supplemental/invalid-numerical-values.json +++ b/adapters/adquery/adquerytest/supplemental/invalid-numerical-values.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,13 +42,16 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", @@ -80,9 +83,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "PLN", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -101,14 +102,18 @@ } } ], - "expectedMakeBidsErrors": [{ - "value": "strconv.ParseFloat: parsing \"$4.14\": invalid syntax", - "comparison": "literal" - },{ - "value": "strconv.ParseInt: parsing \"320px\": invalid syntax", - "comparison": "literal" - },{ - "value": "strconv.ParseInt: parsing \"50px\": invalid syntax", - "comparison": "literal" - }] -} \ No newline at end of file + "expectedMakeBidsErrors": [ + { + "value": "strconv.ParseFloat: parsing \"$4.14\": invalid syntax", + "comparison": "literal" + }, + { + "value": "strconv.ParseInt: parsing \"320px\": invalid syntax", + "comparison": "literal" + }, + { + "value": "strconv.ParseInt: parsing \"50px\": invalid syntax", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/malformed-resp.json b/adapters/adquery/adquerytest/supplemental/malformed-resp.json index f7aa271e6fe..8ab1763c0be 100644 --- a/adapters/adquery/adquerytest/supplemental/malformed-resp.json +++ b/adapters/adquery/adquerytest/supplemental/malformed-resp.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,13 +42,16 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", @@ -80,9 +83,7 @@ "urlAdq": "https://adquery.io", "creationId": "string-identifier", "currency": "PLN", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -101,8 +102,10 @@ } } ], - "expectedMakeBidsErrors": [{ - "value": "json: cannot unmarshal string into Go struct field ResponseData.data.creationId of type int64", - "comparison": "literal" - }] -} \ No newline at end of file + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go struct field ResponseData.data.creationId of type int64", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/mediatype-unknown.json b/adapters/adquery/adquerytest/supplemental/mediatype-unknown.json index bf3cdc63f45..47b05d836f9 100644 --- a/adapters/adquery/adquerytest/supplemental/mediatype-unknown.json +++ b/adapters/adquery/adquerytest/supplemental/mediatype-unknown.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,13 +42,16 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", @@ -80,9 +83,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "PLN", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -101,8 +102,10 @@ } } ], - "expectedMakeBidsErrors": [{ - "value": "unsupported MediaType: unknown", - "comparison": "literal" - }] -} \ No newline at end of file + "expectedMakeBidsErrors": [ + { + "value": "unsupported MediaType: unknown", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/mediatype-video.json b/adapters/adquery/adquerytest/supplemental/mediatype-video.json index 7becebab291..a6d19e7fc5a 100644 --- a/adapters/adquery/adquerytest/supplemental/mediatype-video.json +++ b/adapters/adquery/adquerytest/supplemental/mediatype-video.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,13 +42,16 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", @@ -80,9 +83,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "PLN", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -101,8 +102,10 @@ } } ], - "expectedMakeBidsErrors": [{ - "value": "unsupported MediaType: video", - "comparison": "literal" - }] -} \ No newline at end of file + "expectedMakeBidsErrors": [ + { + "value": "unsupported MediaType: video", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/no-device.json b/adapters/adquery/adquerytest/supplemental/no-device.json new file mode 100644 index 00000000000..9876cd57c88 --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/no-device.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "site": { + "page": "http://www.example.com" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "http://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc1", + "emission_id": "22e26bd9a702bc1", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "EUR", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "22e26bd9a702bc1", + "impid": "1", + "price": 4.14, + "adm": " ", + "adomain": ["https://s1.adquery.io"], + "crid": "7211", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/no-imp-banner-measures.json b/adapters/adquery/adquerytest/supplemental/no-imp-banner-measures.json index 953d6de5d8d..c745c9a7c30 100644 --- a/adapters/adquery/adquerytest/supplemental/no-imp-banner-measures.json +++ b/adapters/adquery/adquerytest/supplemental/no-imp-banner-measures.json @@ -23,7 +23,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -31,13 +31,16 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "", "type": "banner", @@ -58,4 +61,4 @@ "bids": [] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/supplemental/no-imp-banner.json b/adapters/adquery/adquerytest/supplemental/no-imp-banner.json index 8589d97f606..f7de622587f 100644 --- a/adapters/adquery/adquerytest/supplemental/no-imp-banner.json +++ b/adapters/adquery/adquerytest/supplemental/no-imp-banner.json @@ -22,7 +22,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -30,13 +30,16 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "", "type": "banner", @@ -57,4 +60,4 @@ "bids": [] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/supplemental/no-site.json b/adapters/adquery/adquerytest/supplemental/no-site.json new file mode 100644 index 00000000000..20305f52b11 --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/no-site.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc1", + "emission_id": "22e26bd9a702bc1", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "EUR", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "22e26bd9a702bc1", + "impid": "1", + "price": 4.14, + "adm": " ", + "adomain": ["https://s1.adquery.io"], + "crid": "7211", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/resp-bad-request.json b/adapters/adquery/adquerytest/supplemental/resp-bad-request.json index cb869625720..f5e807ebad5 100644 --- a/adapters/adquery/adquerytest/supplemental/resp-bad-request.json +++ b/adapters/adquery/adquerytest/supplemental/resp-bad-request.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,13 +42,16 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", @@ -61,8 +64,10 @@ } } ], - "expectedMakeBidsErrors": [{ - "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", - "comparison": "literal" - }] -} \ No newline at end of file + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/resp-no-content.json b/adapters/adquery/adquerytest/supplemental/resp-no-content.json index 5817e15a533..1ce24c17081 100644 --- a/adapters/adquery/adquerytest/supplemental/resp-no-content.json +++ b/adapters/adquery/adquerytest/supplemental/resp-no-content.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,13 +42,16 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", @@ -61,6 +64,5 @@ } } ], - "expectedBidResponses": [ - ] -} \ No newline at end of file + "expectedBidResponses": [] +} diff --git a/adapters/adquery/adquerytest/supplemental/resp-server-error.json b/adapters/adquery/adquerytest/supplemental/resp-server-error.json index 05c1cae8488..2e8e42ba927 100644 --- a/adapters/adquery/adquerytest/supplemental/resp-server-error.json +++ b/adapters/adquery/adquerytest/supplemental/resp-server-error.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,13 +42,16 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", @@ -61,8 +64,10 @@ } } ], - "expectedMakeBidsErrors": [{ - "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", - "comparison": "literal" - }] -} \ No newline at end of file + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/types.go b/adapters/adquery/types.go index 5013ecf488a..dab9619a7f5 100644 --- a/adapters/adquery/types.go +++ b/adapters/adquery/types.go @@ -10,6 +10,9 @@ type BidderRequest struct { AdUnitCode string `json:"adUnitCode"` BidQid string `json:"bidQid"` BidId string `json:"bidId"` + BidIp string `json:"bidIp"` + BidIpv6 string `json:"bidIpv6"` + BidUa string `json:"bidUa"` Bidder string `json:"bidder"` BidPageUrl string `json:"bidPageUrl"` BidderRequestId string `json:"bidderRequestId"` diff --git a/static/bidder-info/adquery.yaml b/static/bidder-info/adquery.yaml index 98b6b0ea432..035bd6fc1cb 100644 --- a/static/bidder-info/adquery.yaml +++ b/static/bidder-info/adquery.yaml @@ -1,16 +1,13 @@ -endpoint: "https://bidder.adquery.io/prebid/bid" +endpoint: https://bidder2.adquery.io/prebid/bid maintainer: email: prebid@adquery.io -#endpointCompression: gzip # disabled because otherwise bidder responds with {data:null} gvlVendorID: 902 capabilities: -# app: # disabled because currently it's only a site, not an app (?) -# mediaTypes: -# - banner site: mediaTypes: - banner userSync: - redirect: - url: https://bidder.adquery.io/prebid/userSync?1=1&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&ccpa_consent={{.USPrivacy}}&redirect={{.RedirectURL}} - userMacro: $UID \ No newline at end of file + iframe: + url: https://api.adquery.io/storage?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&ccpa_consent={{.USPrivacy}}&redirect={{.RedirectURL}} + userMacro: $UID + \ No newline at end of file From ea01ccff4100235f8af425011785ad7adfdb4a9d Mon Sep 17 00:00:00 2001 From: Vungle-GordonTian <115982294+Vungle-GordonTian@users.noreply.github.com> Date: Mon, 20 Nov 2023 17:01:24 +0800 Subject: [PATCH 103/138] update contact email address (#3298) --- static/bidder-info/liftoff.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/bidder-info/liftoff.yaml b/static/bidder-info/liftoff.yaml index 577439dbb97..43ffd1a7b18 100644 --- a/static/bidder-info/liftoff.yaml +++ b/static/bidder-info/liftoff.yaml @@ -1,6 +1,6 @@ endpoint: "https://rtb.ads.vungle.com/bid/t/c770f32" maintainer: - email: platform-ssp@liftoff.io + email: vxssp@liftoff.io endpointCompression: gzip modifyingVastXmlAllowed: true capabilities: From 1e89053b4780a81fe99242daf153875d4e509fbb Mon Sep 17 00:00:00 2001 From: Maxime Liege <56251840+github-maxime-liege@users.noreply.github.com> Date: Mon, 20 Nov 2023 10:02:09 +0100 Subject: [PATCH 104/138] Change endpoint for Teads adapter (#3303) --- adapters/teads/teads_test.go | 4 ++-- .../teads/teadstest/exemplary/simple-banner-with-format.json | 2 +- adapters/teads/teadstest/exemplary/simple-banner.json | 2 +- adapters/teads/teadstest/exemplary/simple-video.json | 2 +- .../teads/teadstest/supplemental/bid-id-does-not-match.json | 2 +- .../teads/teadstest/supplemental/currency-empty-string.json | 2 +- .../teads/teadstest/supplemental/no-impression-response.json | 2 +- .../teads/teadstest/supplemental/renderer-name-empty.json | 2 +- .../teads/teadstest/supplemental/renderer-version-empty.json | 2 +- adapters/teads/teadstest/supplemental/status-400.json | 2 +- adapters/teads/teadstest/supplemental/status-500.json | 2 +- static/bidder-info/teads.yaml | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/adapters/teads/teads_test.go b/adapters/teads/teads_test.go index 4f492b66b1e..c9f807ace21 100644 --- a/adapters/teads/teads_test.go +++ b/adapters/teads/teads_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderTeads, config.Adapter{ - Endpoint: "https://a.teads.tv/prebid-server/bid-request"}, config.Server{ExternalUrl: "https://a.teads.tv/prebid-server/bid-request", GvlID: 1, DataCenter: "2"}) + Endpoint: "https://psrv.teads.tv/prebid-server/bid-request"}, config.Server{ExternalUrl: "https://psrv.teads.tv/prebid-server/bid-request", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderTeads, config.Adapter{ - Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "https://a.teads.tv/prebid-server/bid-request", GvlID: 1, DataCenter: "2"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "https://psrv.teads.tv/prebid-server/bid-request", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/teads/teadstest/exemplary/simple-banner-with-format.json b/adapters/teads/teadstest/exemplary/simple-banner-with-format.json index b2677e6faba..ffaf849bfd8 100644 --- a/adapters/teads/teadstest/exemplary/simple-banner-with-format.json +++ b/adapters/teads/teadstest/exemplary/simple-banner-with-format.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/teads/teadstest/exemplary/simple-banner.json b/adapters/teads/teadstest/exemplary/simple-banner.json index 2d9be7c7368..43a28614f9c 100644 --- a/adapters/teads/teadstest/exemplary/simple-banner.json +++ b/adapters/teads/teadstest/exemplary/simple-banner.json @@ -35,7 +35,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/teads/teadstest/exemplary/simple-video.json b/adapters/teads/teadstest/exemplary/simple-video.json index 814569a47e1..de273aba904 100644 --- a/adapters/teads/teadstest/exemplary/simple-video.json +++ b/adapters/teads/teadstest/exemplary/simple-video.json @@ -48,7 +48,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json b/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json index 384e72fb537..b2e5c9e7aba 100644 --- a/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json +++ b/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json @@ -48,7 +48,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/teads/teadstest/supplemental/currency-empty-string.json b/adapters/teads/teadstest/supplemental/currency-empty-string.json index 5f0d700b14b..9168f265cae 100644 --- a/adapters/teads/teadstest/supplemental/currency-empty-string.json +++ b/adapters/teads/teadstest/supplemental/currency-empty-string.json @@ -48,7 +48,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/teads/teadstest/supplemental/no-impression-response.json b/adapters/teads/teadstest/supplemental/no-impression-response.json index 814569a47e1..de273aba904 100644 --- a/adapters/teads/teadstest/supplemental/no-impression-response.json +++ b/adapters/teads/teadstest/supplemental/no-impression-response.json @@ -48,7 +48,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/teads/teadstest/supplemental/renderer-name-empty.json b/adapters/teads/teadstest/supplemental/renderer-name-empty.json index da4ee9a5094..9d35cf2fe71 100644 --- a/adapters/teads/teadstest/supplemental/renderer-name-empty.json +++ b/adapters/teads/teadstest/supplemental/renderer-name-empty.json @@ -44,7 +44,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/teads/teadstest/supplemental/renderer-version-empty.json b/adapters/teads/teadstest/supplemental/renderer-version-empty.json index e9e7b278dcb..cb1a47d6fbe 100644 --- a/adapters/teads/teadstest/supplemental/renderer-version-empty.json +++ b/adapters/teads/teadstest/supplemental/renderer-version-empty.json @@ -44,7 +44,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/teads/teadstest/supplemental/status-400.json b/adapters/teads/teadstest/supplemental/status-400.json index cd9fafff0a1..98b1875402c 100644 --- a/adapters/teads/teadstest/supplemental/status-400.json +++ b/adapters/teads/teadstest/supplemental/status-400.json @@ -24,7 +24,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/teads/teadstest/supplemental/status-500.json b/adapters/teads/teadstest/supplemental/status-500.json index 337d4754006..4db6eed0ec8 100644 --- a/adapters/teads/teadstest/supplemental/status-500.json +++ b/adapters/teads/teadstest/supplemental/status-500.json @@ -24,7 +24,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ diff --git a/static/bidder-info/teads.yaml b/static/bidder-info/teads.yaml index 5dc428ddf7b..47686977673 100644 --- a/static/bidder-info/teads.yaml +++ b/static/bidder-info/teads.yaml @@ -1,4 +1,4 @@ -endpoint: "https://a.teads.tv/prebid-server/bid-request" +endpoint: "https://psrv.teads.tv/prebid-server/bid-request" maintainer: email: "support-sdk@teads.com" gvlVendorID: 132 From 06e1145971c81c66c884a77b1fe2cf0752d1d2f0 Mon Sep 17 00:00:00 2001 From: kalidas-alkimi <92875788+kalidas-alkimi@users.noreply.github.com> Date: Mon, 20 Nov 2023 12:38:58 +0000 Subject: [PATCH 105/138] New Adapter: Alkimi (#3247) co-authored by @kalidas-alkimi @pro-nsk --- adapters/alkimi/alkimi.go | 192 ++++++++++++++++++ adapters/alkimi/alkimi_test.go | 57 ++++++ .../alkimitest/exemplary/simple-audio.json | 143 +++++++++++++ .../alkimitest/exemplary/simple-banner.json | 150 ++++++++++++++ .../alkimitest/exemplary/simple-video.json | 147 ++++++++++++++ .../supplemental/bad_media_type.json | 108 ++++++++++ .../alkimitest/supplemental/bad_response.json | 101 +++++++++ .../alkimitest/supplemental/status-204.json | 96 +++++++++ .../supplemental/status-not-200.json | 101 +++++++++ adapters/alkimi/params_test.go | 48 +++++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_alkimi.go | 9 + static/bidder-info/alkimi.yaml | 15 ++ static/bidder-params/alkimi.json | 28 +++ 15 files changed, 1199 insertions(+) create mode 100644 adapters/alkimi/alkimi.go create mode 100644 adapters/alkimi/alkimi_test.go create mode 100644 adapters/alkimi/alkimitest/exemplary/simple-audio.json create mode 100644 adapters/alkimi/alkimitest/exemplary/simple-banner.json create mode 100644 adapters/alkimi/alkimitest/exemplary/simple-video.json create mode 100644 adapters/alkimi/alkimitest/supplemental/bad_media_type.json create mode 100644 adapters/alkimi/alkimitest/supplemental/bad_response.json create mode 100644 adapters/alkimi/alkimitest/supplemental/status-204.json create mode 100644 adapters/alkimi/alkimitest/supplemental/status-not-200.json create mode 100644 adapters/alkimi/params_test.go create mode 100644 openrtb_ext/imp_alkimi.go create mode 100644 static/bidder-info/alkimi.yaml create mode 100644 static/bidder-params/alkimi.json diff --git a/adapters/alkimi/alkimi.go b/adapters/alkimi/alkimi.go new file mode 100644 index 00000000000..f0979c3a809 --- /dev/null +++ b/adapters/alkimi/alkimi.go @@ -0,0 +1,192 @@ +package alkimi + +import ( + "encoding/json" + "fmt" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/floors" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +const price_macro = "${AUCTION_PRICE}" + +type adapter struct { + endpoint string +} + +type extObj struct { + AlkimiBidderExt openrtb_ext.ExtImpAlkimi `json:"bidder"` +} + +// Builder builds a new instance of the Alkimi adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + endpointURL, err := url.Parse(config.Endpoint) + if err != nil || len(endpointURL.String()) == 0 { + return nil, fmt.Errorf("invalid endpoint: %v", err) + } + + bidder := &adapter{ + endpoint: endpointURL.String(), + } + return bidder, nil +} + +// MakeRequests creates Alkimi adapter requests +func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) { + reqCopy := *request + + updated, errs := updateImps(reqCopy) + if len(errs) > 0 || len(reqCopy.Imp) != len(updated) { + return nil, errs + } + + reqCopy.Imp = updated + encoded, err := json.Marshal(reqCopy) + if err != nil { + errs = append(errs, err) + } else { + reqBidder := buildBidderRequest(adapter, encoded) + reqsBidder = append(reqsBidder, reqBidder) + } + return +} + +func updateImps(bidRequest openrtb2.BidRequest) ([]openrtb2.Imp, []error) { + var errs []error + + updatedImps := make([]openrtb2.Imp, 0, len(bidRequest.Imp)) + for _, imp := range bidRequest.Imp { + + var bidderExt adapters.ExtImpBidder + var extImpAlkimi openrtb_ext.ExtImpAlkimi + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, err) + continue + } + + if err := json.Unmarshal(bidderExt.Bidder, &extImpAlkimi); err != nil { + errs = append(errs, err) + continue + } + + var bidFloorPrice floors.Price + bidFloorPrice.FloorMinCur = imp.BidFloorCur + bidFloorPrice.FloorMin = imp.BidFloor + + if len(bidFloorPrice.FloorMinCur) > 0 && bidFloorPrice.FloorMin > 0 { + imp.BidFloor = bidFloorPrice.FloorMin + } else { + imp.BidFloor = extImpAlkimi.BidFloor + } + imp.Instl = extImpAlkimi.Instl + imp.Exp = extImpAlkimi.Exp + + temp := extObj{AlkimiBidderExt: extImpAlkimi} + temp.AlkimiBidderExt.AdUnitCode = imp.ID + + extJson, err := json.Marshal(temp) + if err != nil { + errs = append(errs, err) + continue + } + imp.Ext = extJson + updatedImps = append(updatedImps, imp) + } + + return updatedImps, errs +} + +func buildBidderRequest(adapter *adapter, encoded []byte) *adapters.RequestData { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + reqBidder := &adapters.RequestData{ + Method: "POST", + Uri: adapter.endpoint, + Body: encoded, + Headers: headers, + } + return reqBidder +} + +// MakeBids will parse the bids from the Alkimi server +func (adapter *adapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + if adapters.IsResponseStatusCodeNoContent(response) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + err := json.Unmarshal(response.Body, &bidResp) + if err != nil { + return nil, []error{err} + } + + seatBidCount := len(bidResp.SeatBid) + if seatBidCount == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid array", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + for _, seatBid := range bidResp.SeatBid { + for _, bid := range seatBid.Bid { + copyBid := bid + resolveMacros(©Bid) + impId := copyBid.ImpID + imp := request.Imp + bidType, err := getMediaTypeForImp(impId, imp) + if err != nil { + errs = append(errs, err) + continue + } + bidderBid := &adapters.TypedBid{ + Bid: ©Bid, + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, bidderBid) + } + } + return bidResponse, errs +} + +func resolveMacros(bid *openrtb2.Bid) { + strPrice := strconv.FormatFloat(bid.Price, 'f', -1, 64) + bid.NURL = strings.Replace(bid.NURL, price_macro, strPrice, -1) + bid.AdM = strings.Replace(bid.AdM, price_macro, strPrice, -1) +} + +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impId { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + if imp.Audio != nil { + return openrtb_ext.BidTypeAudio, nil + } + } + } + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find imp \"%s\"", impId), + } +} diff --git a/adapters/alkimi/alkimi_test.go b/adapters/alkimi/alkimi_test.go new file mode 100644 index 00000000000..b745f0feb95 --- /dev/null +++ b/adapters/alkimi/alkimi_test.go @@ -0,0 +1,57 @@ +package alkimi + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +const ( + alkimiTestEndpoint = "https://exchange.alkimi-onboarding.com/server/bid" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder( + openrtb_ext.BidderAlkimi, + config.Adapter{Endpoint: alkimiTestEndpoint}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}, + ) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "alkimitest", bidder) +} + +func TestEndpointEmpty(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAlkimi, config.Adapter{ + Endpoint: ""}, config.Server{ExternalUrl: alkimiTestEndpoint, GvlID: 1, DataCenter: "2"}) + assert.Error(t, buildErr) +} + +func TestEndpointMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAlkimi, config.Adapter{ + Endpoint: " http://leading.space.is.invalid"}, config.Server{ExternalUrl: alkimiTestEndpoint, GvlID: 1, DataCenter: "2"}) + assert.Error(t, buildErr) +} + +func TestBuilder(t *testing.T) { + bidder, buildErr := buildBidder() + if buildErr != nil { + t.Fatalf("Failed to build bidder: %v", buildErr) + } + assert.NotNil(t, bidder) +} + +func buildBidder() (adapters.Bidder, error) { + return Builder( + openrtb_ext.BidderAlkimi, + config.Adapter{Endpoint: alkimiTestEndpoint}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}, + ) +} diff --git a/adapters/alkimi/alkimitest/exemplary/simple-audio.json b/adapters/alkimi/alkimitest/exemplary/simple-audio.json new file mode 100644 index 00000000000..09ac2131a12 --- /dev/null +++ b/adapters/alkimi/alkimitest/exemplary/simple-audio.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "audio": { + "mimes": [ + "audio/mpeg", + "audio/mp3" + ], + "minduration": 5, + "maxduration": 30, + "minbitrate": 32, + "maxbitrate": 128 + }, + "bidfloor": 0.7, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "token": "XXX", + "bidFloor": 0.5 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://exchange.alkimi-onboarding.com/server/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "audio": { + "mimes": [ + "audio/mpeg", + "audio/mp3" + ], + "minduration": 5, + "maxduration": 30, + "minbitrate": 32, + "maxbitrate": 128 + }, + "bidfloor": 0.7, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "token": "XXX", + "bidFloor": 0.5, + "adUnitCode": "test-imp-id", + "exp": 0, + "instl": 0 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.9, + "adm": "<\/Error><\/Impression>00:00:15<\/Duration><\/Tracking><\/TrackingEvents><\/ClickThrough><\/VideoClicks><\/MediaFile><\/MediaFiles><\/Linear><\/Creative><\/Creatives><\/InLine><\/Ad><\/VAST>", + "cid": "test_cid", + "crid": "test_crid", + "ext": { + "prebid": { + "type": "audio" + } + } + } + ], + "seat": "alkimi" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.9, + "adm": "<\/Error><\/Impression>00:00:15<\/Duration><\/Tracking><\/TrackingEvents><\/ClickThrough><\/VideoClicks><\/MediaFile><\/MediaFiles><\/Linear><\/Creative><\/Creatives><\/InLine><\/Ad><\/VAST>", + "cid": "test_cid", + "crid": "test_crid", + "ext": { + "prebid": { + "type": "audio" + } + } + }, + "type": "audio" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/alkimi/alkimitest/exemplary/simple-banner.json b/adapters/alkimi/alkimitest/exemplary/simple-banner.json new file mode 100644 index 00000000000..96512989596 --- /dev/null +++ b/adapters/alkimi/alkimitest/exemplary/simple-banner.json @@ -0,0 +1,150 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "token": "XXX", + "bidFloor": 0.5 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://exchange.alkimi-onboarding.com/server/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 0.5, + "ext": { + "bidder": { + "token": "XXX", + "bidFloor": 0.5, + "adUnitCode": "test-imp-id", + "exp": 0, + "instl": 0 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.9, + "adm": "
<\/a><\/div>\"}", - "ext": { - "prebid": { - "type": "native" - } - } - } - ], - "seat": "123" - } - ], - "bidid": "8141327771600527856", - "cur": "EUR" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "EUR", - "bids": [ - { - "bid": { - "id": "some-id", - "impid": "test-imp-id", - "price": 1, - "adid": "69595837", - "adm": "{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 3000,\"h\": 2250}},{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=1/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=1\"],\"jstracker\":\"\"}", - "ext": { - "prebid": { - "type": "native" - } - } - }, - "type": "native" - } - ] - } - ] -} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json b/adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json deleted file mode 100644 index 1493d336f9d..00000000000 --- a/adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "expectedMakeRequestsErrors": [ - { - "value": "could not unmarshal openrtb_ext.ImpExtSuntContent: json: cannot unmarshal number into Go struct field ImpExtSuntContent.adUnitID of type string", - "comparison": "literal" - } - ], - "mockBidRequest": { - "id": "test-request-id", - "site": { - "publisher": { - "id": "foo", - "name": "foo" - } - }, - "imp": [ - { - "id": "test-imp-id", - "native": { - "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" - }, - "ext": { - "bidder": { - "adUnitId": 1234 - } - } - } - ] - } -} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/supplemental/status_bad_request.json b/adapters/suntContent/suntContenttest/supplemental/status_bad_request.json deleted file mode 100644 index d7e62bb90cb..00000000000 --- a/adapters/suntContent/suntContenttest/supplemental/status_bad_request.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://mockup.suntcontent.com/", - "body": { - "cur": [ - "EUR" - ], - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "example-tag-id", - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - } - }, - "mockResponse": { - "status": 400 - } - } - ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/supplemental/status_no_content.json b/adapters/suntContent/suntContenttest/supplemental/status_no_content.json deleted file mode 100644 index 8eda774c657..00000000000 --- a/adapters/suntContent/suntContenttest/supplemental/status_no_content.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://mockup.suntcontent.com/", - "body": { - "cur": [ - "EUR" - ], - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "example-tag-id", - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - } - }, - "mockResponse": { - "status": 204 - } - } - ] -} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/supplemental/status_not_ok.json b/adapters/suntContent/suntContenttest/supplemental/status_not_ok.json deleted file mode 100644 index 07618aed7fa..00000000000 --- a/adapters/suntContent/suntContenttest/supplemental/status_not_ok.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://mockup.suntcontent.com/", - "body": { - "cur": [ - "EUR" - ], - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "example-tag-id", - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - } - }, - "mockResponse": { - "status": 404 - } - } - ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 404. Run with request.debug = 1 for more info.", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index d8fd57c3187..5de2d78362e 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -163,7 +163,6 @@ import ( "github.com/prebid/prebid-server/v2/adapters/sovrn" "github.com/prebid/prebid-server/v2/adapters/sspBC" "github.com/prebid/prebid-server/v2/adapters/stroeerCore" - "github.com/prebid/prebid-server/v2/adapters/suntContent" "github.com/prebid/prebid-server/v2/adapters/taboola" "github.com/prebid/prebid-server/v2/adapters/tappx" "github.com/prebid/prebid-server/v2/adapters/teads" @@ -361,7 +360,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderSonobi: sonobi.Builder, openrtb_ext.BidderSovrn: sovrn.Builder, openrtb_ext.BidderSspBC: sspBC.Builder, - openrtb_ext.BidderSuntContent: suntContent.Builder, openrtb_ext.BidderStroeerCore: stroeerCore.Builder, openrtb_ext.BidderTaboola: taboola.Builder, openrtb_ext.BidderTappx: tappx.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 9c2afdae13e..cc4e095a93e 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -181,7 +181,6 @@ var coreBidderNames []BidderName = []BidderName{ BidderSovrn, BidderSspBC, BidderStroeerCore, - BidderSuntContent, BidderTaboola, BidderTappx, BidderTeads, @@ -460,7 +459,6 @@ const ( BidderSovrn BidderName = "sovrn" BidderSspBC BidderName = "sspBC" BidderStroeerCore BidderName = "stroeerCore" - BidderSuntContent BidderName = "suntContent" BidderTaboola BidderName = "taboola" BidderTappx BidderName = "tappx" BidderTeads BidderName = "teads" @@ -625,7 +623,7 @@ func NewBidderParamsValidator(schemaDirectory string) (BidderParamValidator, err schemaContents[BidderName(bidderName)] = string(fileBytes) } - //set alias bidder params schema to its parent + // set alias bidder params schema to its parent for alias, parent := range aliasBidderToParent { parentSchema := schemas[parent] schemas[alias] = parentSchema diff --git a/openrtb_ext/imp_seedingAlliance.go b/openrtb_ext/imp_seedingAlliance.go index 759683ad6b3..0f594d3c933 100644 --- a/openrtb_ext/imp_seedingAlliance.go +++ b/openrtb_ext/imp_seedingAlliance.go @@ -1,5 +1,6 @@ package openrtb_ext type ImpExtSeedingAlliance struct { - AdUnitID string `json:"adUnitID"` + AdUnitID string `json:"adUnitId"` + SeatID string `json:"seatId"` } diff --git a/openrtb_ext/imp_suntContent.go b/openrtb_ext/imp_suntContent.go deleted file mode 100644 index 5040df7e3b6..00000000000 --- a/openrtb_ext/imp_suntContent.go +++ /dev/null @@ -1,5 +0,0 @@ -package openrtb_ext - -type ImpExtSuntContent struct { - AdUnitID string `json:"adUnitID"` -} diff --git a/static/bidder-info/finative.yaml b/static/bidder-info/finative.yaml new file mode 100644 index 00000000000..e1136c3220d --- /dev/null +++ b/static/bidder-info/finative.yaml @@ -0,0 +1,2 @@ +endpoint: "https://b.finative.cloud/cds/rtb/bid?ssp={{.AccountID}}" +aliasOf: "seedingAlliance" \ No newline at end of file diff --git a/static/bidder-info/seedingAlliance.yaml b/static/bidder-info/seedingAlliance.yaml index dee9fcd6026..9dcb7701b34 100644 --- a/static/bidder-info/seedingAlliance.yaml +++ b/static/bidder-info/seedingAlliance.yaml @@ -1,4 +1,4 @@ -endpoint: "https://b.nativendo.de/cds/rtb/bid?ssp=pbs" +endpoint: "https://b.nativendo.de/cds/rtb/bid?ssp={{.AccountID}}" maintainer: email: tech@seeding-alliance.de gvlVendorID: 371 diff --git a/static/bidder-info/suntContent.yaml b/static/bidder-info/suntContent.yaml index e46cc4086e0..bb5eb2ee977 100644 --- a/static/bidder-info/suntContent.yaml +++ b/static/bidder-info/suntContent.yaml @@ -1,13 +1,7 @@ -endpoint: "https://b.suntcontent.se/cds/rtb/bid?ssp=pbs" -maintainer: - email: tech@seeding-alliance.de +endpoint: "https://b.suntcontent.se/cds/rtb/bid?ssp={{.AccountID}}" +aliasOf: "seedingAlliance" gvlVendorID: 1097 -capabilities: - site: - mediaTypes: - - native - - banner userSync: redirect: url: "https://dmp.suntcontent.se/set-uuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect_url={{.RedirectURL}}" - userMacro: "$UID" + userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-params/seedingAlliance.json b/static/bidder-params/seedingAlliance.json index cf9aa375eb4..5fe463b6803 100644 --- a/static/bidder-params/seedingAlliance.json +++ b/static/bidder-params/seedingAlliance.json @@ -8,6 +8,10 @@ "type": "string", "description": "Ad Unit ID", "minLength": 1 + }, + "seatId": { + "type": "string", + "description": "Seat ID" } }, "required": [ diff --git a/static/bidder-params/suntContent.json b/static/bidder-params/suntContent.json deleted file mode 100644 index e85ad3f5de5..00000000000 --- a/static/bidder-params/suntContent.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "SUNT Content Adapter Params", - "description": "A schema which validates params accepted by the SUNT Content adapter", - "type": "object", - "properties": { - "adUnitId": { - "type": "string", - "description": "Ad Unit ID", - "minLength": 1 - } - }, - "required": [ - "adUnitId" - ] -} \ No newline at end of file diff --git a/usersync/chooser_test.go b/usersync/chooser_test.go index abf49b7f7c2..9bc06100e81 100644 --- a/usersync/chooser_test.go +++ b/usersync/chooser_test.go @@ -66,7 +66,8 @@ func TestChooserChoose(t *testing.T) { syncTypeFilter := SyncTypeFilter{ IFrame: NewUniformBidderFilter(BidderFilterModeInclude), - Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} + Redirect: NewUniformBidderFilter(BidderFilterModeExclude), + } cooperativeConfig := Cooperative{Enabled: true} @@ -455,11 +456,12 @@ func TestChooserEvaluate(t *testing.T) { fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: false} biddersKnown := map[string]struct{}{"a": {}, "b": {}, "unconfigured": {}} - bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "appnexus": fakeSyncerA, "suntContent": fakeSyncerA} + bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "appnexus": fakeSyncerA, "seedingAlliance": fakeSyncerA} syncTypeFilter := SyncTypeFilter{ IFrame: NewUniformBidderFilter(BidderFilterModeInclude), - Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} + Redirect: NewUniformBidderFilter(BidderFilterModeExclude), + } normalizedBidderNamesLookup := func(name string) (openrtb_ext.BidderName, bool) { return openrtb_ext.BidderName(name), true } @@ -593,17 +595,18 @@ func TestChooserEvaluate(t *testing.T) { }, { description: "Case insensitivity check for sync type filter", - givenBidder: "SuntContent", - normalisedBidderName: "suntContent", + givenBidder: "SeedingAlliance", + normalisedBidderName: "seedingAlliance", givenSyncersSeen: map[string]struct{}{}, givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, givenCookie: cookieNeedsSync, givenSyncTypeFilter: SyncTypeFilter{ - IFrame: NewSpecificBidderFilter([]string{"SuntContent"}, BidderFilterModeInclude), - Redirect: NewSpecificBidderFilter([]string{"SuntContent"}, BidderFilterModeExclude)}, + IFrame: NewSpecificBidderFilter([]string{"SeedingAlliance"}, BidderFilterModeInclude), + Redirect: NewSpecificBidderFilter([]string{"SeedingAlliance"}, BidderFilterModeExclude), + }, normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, expectedSyncer: fakeSyncerA, - expectedEvaluation: BidderEvaluation{Bidder: "SuntContent", SyncerKey: "keyA", Status: StatusOK}, + expectedEvaluation: BidderEvaluation{Bidder: "SeedingAlliance", SyncerKey: "keyA", Status: StatusOK}, }, { description: "Case Insensitivity Check For Blocked By GDPR", From ec873dcc87a93c380733c5cd96a75d1b12f920d8 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Mon, 11 Dec 2023 13:38:33 -0800 Subject: [PATCH 130/138] Modules activities (#3057) --- endpoints/openrtb2/amp_auction.go | 2 + endpoints/openrtb2/auction.go | 2 + exchange/utils.go | 32 +---- hooks/hookexecution/context.go | 12 +- hooks/hookexecution/execution.go | 33 ++++- hooks/hookexecution/execution_test.go | 151 +++++++++++++++++++++ hooks/hookexecution/executor.go | 34 +++-- hooks/hookexecution/executor_test.go | 50 ++++++- hooks/hookstage/bidderrequest.go | 14 ++ hooks/hookstage/processedauctionrequest.go | 9 +- ortb/clone.go | 31 +++++ ortb/clone_test.go | 52 +++++++ 12 files changed, 371 insertions(+), 51 deletions(-) create mode 100644 hooks/hookexecution/execution_test.go diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 8f8d32c8fb9..db427f380ed 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -234,6 +234,8 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h activityControl = privacy.NewActivityControl(&account.Privacy) + hookExecutor.SetActivityControl(activityControl) + secGPC := r.Header.Get("Sec-GPC") auctionRequest := &exchange.AuctionRequest{ diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 72382f36b04..eb3f5c02ccb 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -205,6 +205,8 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http activityControl = privacy.NewActivityControl(&account.Privacy) + hookExecutor.SetActivityControl(activityControl) + ctx := context.Background() timeout := deps.cfg.AuctionTimeouts.LimitAuctionTimeout(time.Duration(req.TMax) * time.Millisecond) diff --git a/exchange/utils.go b/exchange/utils.go index dfe1fe448ba..53890718a1d 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/v2/ortb" "math/rand" "strings" @@ -21,7 +22,6 @@ import ( "github.com/prebid/prebid-server/v2/gdpr" "github.com/prebid/prebid-server/v2/metrics" "github.com/prebid/prebid-server/v2/openrtb_ext" - "github.com/prebid/prebid-server/v2/ortb" "github.com/prebid/prebid-server/v2/privacy" "github.com/prebid/prebid-server/v2/privacy/ccpa" "github.com/prebid/prebid-server/v2/privacy/lmt" @@ -181,7 +181,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, // FPD should be applied before policies, otherwise it overrides policies and activities restricted data applyFPD(auctionReq.FirstPartyData, bidderRequest) - reqWrapper := cloneBidderReq(bidderRequest.BidRequest) + reqWrapper := ortb.CloneBidderReq(bidderRequest.BidRequest) passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scopedName, privacy.NewRequestFromBidRequest(*req)) if !passIDActivityAllowed { @@ -238,34 +238,6 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, return } -// cloneBidderReq - clones bidder request and replaces req.User and req.Device with new copies -func cloneBidderReq(req *openrtb2.BidRequest) *openrtb_ext.RequestWrapper { - - // bidder request may be modified differently per bidder based on privacy configs - // new request should be created for each bidder request - // pointer fields like User and Device should be cloned and set back to the request copy - var newReq *openrtb2.BidRequest - newReq = ptrutil.Clone(req) - - if req.User != nil { - userCopy := ortb.CloneUser(req.User) - newReq.User = userCopy - } - - if req.Device != nil { - deviceCopy := ortb.CloneDevice(req.Device) - newReq.Device = deviceCopy - } - - if req.Source != nil { - sourceCopy := ortb.CloneSource(req.Source) - newReq.Source = sourceCopy - } - - reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: newReq} - return reqWrapper -} - func shouldSetLegacyPrivacy(bidderInfo config.BidderInfos, bidder string) bool { binfo, defined := bidderInfo[bidder] diff --git a/hooks/hookexecution/context.go b/hooks/hookexecution/context.go index 0817078137f..f7b6a9d32e1 100644 --- a/hooks/hookexecution/context.go +++ b/hooks/hookexecution/context.go @@ -6,15 +6,17 @@ import ( "github.com/golang/glog" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/privacy" ) // executionContext holds information passed to module's hook during hook execution. type executionContext struct { - endpoint string - stage string - accountId string - account *config.Account - moduleContexts *moduleContexts + endpoint string + stage string + accountID string + account *config.Account + moduleContexts *moduleContexts + activityControl privacy.ActivityControl } func (ctx executionContext) getModuleContext(moduleName string) hookstage.ModuleInvocationContext { diff --git a/hooks/hookexecution/execution.go b/hooks/hookexecution/execution.go index 90ee9b46a9c..05cc5fb5943 100644 --- a/hooks/hookexecution/execution.go +++ b/hooks/hookexecution/execution.go @@ -3,6 +3,7 @@ package hookexecution import ( "context" "fmt" + "github.com/prebid/prebid-server/v2/ortb" "strings" "sync" "time" @@ -10,6 +11,7 @@ import ( "github.com/prebid/prebid-server/v2/hooks" "github.com/prebid/prebid-server/v2/hooks/hookstage" "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/privacy" ) type hookResponse[T any] struct { @@ -66,10 +68,11 @@ func executeGroup[H any, P any]( for _, hook := range group.Hooks { mCtx := executionCtx.getModuleContext(hook.Module) + newPayload := handleModuleActivities(hook.Code, executionCtx.activityControl, payload) wg.Add(1) go func(hw hooks.HookWrapper[H], moduleCtx hookstage.ModuleInvocationContext) { defer wg.Done() - executeHook(moduleCtx, hw, payload, hookHandler, group.Timeout, resp, rejected) + executeHook(moduleCtx, hw, newPayload, hookHandler, group.Timeout, resp, rejected) }(hook, mCtx) } @@ -176,7 +179,7 @@ func handleHookResponse[P any]( metricEngine metrics.MetricsEngine, ) (P, HookOutcome, *RejectError) { var rejectErr *RejectError - labels := metrics.ModuleLabels{Module: moduleReplacer.Replace(hr.HookID.ModuleCode), Stage: ctx.stage, AccountID: ctx.accountId} + labels := metrics.ModuleLabels{Module: moduleReplacer.Replace(hr.HookID.ModuleCode), Stage: ctx.stage, AccountID: ctx.accountID} metricEngine.RecordModuleCalled(labels, hr.ExecutionTime) hookOutcome := HookOutcome{ @@ -311,3 +314,29 @@ func handleHookMutations[P any]( return payload } + +func handleModuleActivities[P any](hookCode string, activityControl privacy.ActivityControl, payload P) P { + payloadData, ok := any(&payload).(hookstage.RequestUpdater) + if !ok { + return payload + } + + scopeGeneral := privacy.Component{Type: privacy.ComponentTypeGeneral, Name: hookCode} + transmitUserFPDActivityAllowed := activityControl.Allow(privacy.ActivityTransmitUserFPD, scopeGeneral, privacy.ActivityRequest{}) + + if !transmitUserFPDActivityAllowed { + // changes need to be applied to new payload and leave original payload unchanged + bidderReq := payloadData.GetBidderRequestPayload() + + bidderReqCopy := ortb.CloneBidderReq(bidderReq.BidRequest) + + privacy.ScrubUserFPD(bidderReqCopy) + + var newPayload = payload + var np = any(&newPayload).(hookstage.RequestUpdater) + np.SetBidderRequestPayload(bidderReqCopy) + return newPayload + } + + return payload +} diff --git a/hooks/hookexecution/execution_test.go b/hooks/hookexecution/execution_test.go new file mode 100644 index 00000000000..a12175e30a0 --- /dev/null +++ b/hooks/hookexecution/execution_test.go @@ -0,0 +1,151 @@ +package hookexecution + +import ( + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestHandleModuleActivitiesBidderRequestPayload(t *testing.T) { + + testCases := []struct { + description string + hookCode string + privacyConfig *config.AccountPrivacy + inPayloadData hookstage.BidderRequestPayload + expectedPayloadData hookstage.BidderRequestPayload + }{ + { + description: "payload should change when userFPD is blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + privacyConfig: getTransmitUFPDActivityConfig("foo", false), + expectedPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: ""}, + }, + }}, + }, + { + description: "payload should not change when userFPD is not blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + privacyConfig: getTransmitUFPDActivityConfig("foo", true), + expectedPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + }, + } + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + //check input payload didn't change + origInPayloadData := test.inPayloadData + activityControl := privacy.NewActivityControl(test.privacyConfig) + newPayload := handleModuleActivities(test.hookCode, activityControl, test.inPayloadData) + assert.Equal(t, test.expectedPayloadData.Request.BidRequest, newPayload.Request.BidRequest) + assert.Equal(t, origInPayloadData, test.inPayloadData) + }) + } +} + +func TestHandleModuleActivitiesProcessedAuctionRequestPayload(t *testing.T) { + + testCases := []struct { + description string + hookCode string + privacyConfig *config.AccountPrivacy + inPayloadData hookstage.ProcessedAuctionRequestPayload + expectedPayloadData hookstage.ProcessedAuctionRequestPayload + }{ + { + description: "payload should change when userFPD is blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + privacyConfig: getTransmitUFPDActivityConfig("foo", false), + expectedPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: ""}, + }}, + }, + }, + { + description: "payload should not change when userFPD is not blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + privacyConfig: getTransmitUFPDActivityConfig("foo", true), + expectedPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + }, + } + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + //check input payload didn't change + origInPayloadData := test.inPayloadData + activityControl := privacy.NewActivityControl(test.privacyConfig) + newPayload := handleModuleActivities(test.hookCode, activityControl, test.inPayloadData) + assert.Equal(t, test.expectedPayloadData.Request.BidRequest, newPayload.Request.BidRequest) + assert.Equal(t, origInPayloadData, test.inPayloadData) + }) + } +} + +func TestHandleModuleActivitiesNoBidderRequestPayload(t *testing.T) { + + testCases := []struct { + description string + hookCode string + privacyConfig *config.AccountPrivacy + inPayloadData hookstage.RawAuctionRequestPayload + expectedPayloadData hookstage.RawAuctionRequestPayload + }{ + { + description: "payload should change when userFPD is blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.RawAuctionRequestPayload{}, + privacyConfig: getTransmitUFPDActivityConfig("foo", false), + expectedPayloadData: hookstage.RawAuctionRequestPayload{}, + }, + { + description: "payload should not change when userFPD is not blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.RawAuctionRequestPayload{}, + privacyConfig: getTransmitUFPDActivityConfig("foo", true), + expectedPayloadData: hookstage.RawAuctionRequestPayload{}, + }, + } + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + //check input payload didn't change + origInPayloadData := test.inPayloadData + activityControl := privacy.NewActivityControl(test.privacyConfig) + newPayload := handleModuleActivities(test.hookCode, activityControl, test.inPayloadData) + assert.Equal(t, test.expectedPayloadData, newPayload) + assert.Equal(t, origInPayloadData, test.inPayloadData) + }) + } +} diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go index f820b79fdb2..518a0c628a6 100644 --- a/hooks/hookexecution/executor.go +++ b/hooks/hookexecution/executor.go @@ -13,6 +13,7 @@ import ( "github.com/prebid/prebid-server/v2/hooks/hookstage" "github.com/prebid/prebid-server/v2/metrics" "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" ) const ( @@ -43,17 +44,19 @@ type StageExecutor interface { type HookStageExecutor interface { StageExecutor SetAccount(account *config.Account) + SetActivityControl(activityControl privacy.ActivityControl) GetOutcomes() []StageOutcome } type hookExecutor struct { - account *config.Account - accountID string - endpoint string - planBuilder hooks.ExecutionPlanBuilder - stageOutcomes []StageOutcome - moduleContexts *moduleContexts - metricEngine metrics.MetricsEngine + account *config.Account + accountID string + endpoint string + planBuilder hooks.ExecutionPlanBuilder + stageOutcomes []StageOutcome + moduleContexts *moduleContexts + metricEngine metrics.MetricsEngine + activityControl privacy.ActivityControl // Mutex needed for BidderRequest and RawBidderResponse Stages as they are run in several goroutines sync.Mutex } @@ -77,6 +80,10 @@ func (e *hookExecutor) SetAccount(account *config.Account) { e.accountID = account.ID } +func (e *hookExecutor) SetActivityControl(activityControl privacy.ActivityControl) { + e.activityControl = activityControl +} + func (e *hookExecutor) GetOutcomes() []StageOutcome { return e.stageOutcomes } @@ -290,11 +297,12 @@ func (e *hookExecutor) ExecuteAuctionResponseStage(response *openrtb2.BidRespons func (e *hookExecutor) newContext(stage string) executionContext { return executionContext{ - account: e.account, - accountId: e.accountID, - endpoint: e.endpoint, - moduleContexts: e.moduleContexts, - stage: stage, + account: e.account, + accountID: e.accountID, + endpoint: e.endpoint, + moduleContexts: e.moduleContexts, + stage: stage, + activityControl: e.activityControl, } } @@ -316,6 +324,8 @@ type EmptyHookExecutor struct{} func (executor EmptyHookExecutor) SetAccount(_ *config.Account) {} +func (executor EmptyHookExecutor) SetActivityControl(_ privacy.ActivityControl) {} + func (executor EmptyHookExecutor) GetOutcomes() []StageOutcome { return []StageOutcome{} } diff --git a/hooks/hookexecution/executor_test.go b/hooks/hookexecution/executor_test.go index 90fa09e394f..1fb299204ec 100644 --- a/hooks/hookexecution/executor_test.go +++ b/hooks/hookexecution/executor_test.go @@ -18,6 +18,8 @@ import ( "github.com/prebid/prebid-server/v2/metrics" metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -674,8 +676,11 @@ func TestExecuteRawAuctionStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) - exec.SetAccount(test.givenAccount) + privacyConfig := getTransmitUFPDActivityConfig("foo", false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) + exec.SetAccount(test.givenAccount) newBody, reject := exec.ExecuteRawAuctionStage([]byte(test.givenBody)) assert.Equal(t, test.expectedReject, reject, "Unexpected stage reject.") @@ -896,6 +901,10 @@ func TestExecuteProcessedAuctionStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(ti *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + + privacyConfig := getTransmitUFPDActivityConfig("foo", false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) exec.SetAccount(test.givenAccount) err := exec.ExecuteProcessedAuctionStage(&test.givenRequest) @@ -938,6 +947,7 @@ func TestExecuteBidderRequestStage(t *testing.T) { expectedReject *RejectError expectedModuleContexts *moduleContexts expectedStageOutcomes []StageOutcome + privacyConfig *config.AccountPrivacy }{ { description: "Payload not changed if hook execution plan empty", @@ -1169,6 +1179,9 @@ func TestExecuteBidderRequestStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + privacyConfig := getTransmitUFPDActivityConfig("foo", false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) exec.SetAccount(test.givenAccount) reject := exec.ExecuteBidderRequestStage(&openrtb_ext.RequestWrapper{BidRequest: test.givenBidderRequest}, bidderName) @@ -1187,6 +1200,29 @@ func TestExecuteBidderRequestStage(t *testing.T) { } } +func getTransmitUFPDActivityConfig(componentName string, allow bool) *config.AccountPrivacy { + return &config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + TransmitUserFPD: buildDefaultActivityConfig(componentName, allow), + }, + } +} + +func buildDefaultActivityConfig(componentName string, allow bool) config.Activity { + return config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: allow, + Condition: config.ActivityCondition{ + ComponentName: []string{componentName}, + ComponentType: []string{"general"}, + }, + }, + }, + } +} + func TestExecuteRawBidderResponseStage(t *testing.T) { foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} account := &config.Account{} @@ -1390,6 +1426,10 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(ti *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + + privacyConfig := getTransmitUFPDActivityConfig("foo", false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) exec.SetAccount(test.givenAccount) reject := exec.ExecuteRawBidderResponseStage(&test.givenBidderResponse, "the-bidder") @@ -1669,6 +1709,10 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + + privacyConfig := getTransmitUFPDActivityConfig("foo", false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) exec.SetAccount(test.givenAccount) exec.ExecuteAllProcessedBidResponsesStage(test.givenBiddersResponse) @@ -1918,6 +1962,10 @@ func TestExecuteAuctionResponseStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + + privacyConfig := getTransmitUFPDActivityConfig("foo", false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) exec.SetAccount(test.givenAccount) exec.ExecuteAuctionResponseStage(test.givenResponse) diff --git a/hooks/hookstage/bidderrequest.go b/hooks/hookstage/bidderrequest.go index af480c5410c..05f3574c8bf 100644 --- a/hooks/hookstage/bidderrequest.go +++ b/hooks/hookstage/bidderrequest.go @@ -27,3 +27,17 @@ type BidderRequestPayload struct { Request *openrtb_ext.RequestWrapper Bidder string } + +func (brp *BidderRequestPayload) GetBidderRequestPayload() *openrtb_ext.RequestWrapper { + return brp.Request +} + +func (brp *BidderRequestPayload) SetBidderRequestPayload(br *openrtb_ext.RequestWrapper) { + brp.Request = br +} + +// RequestUpdater allows reading and writing a bid request +type RequestUpdater interface { + GetBidderRequestPayload() *openrtb_ext.RequestWrapper + SetBidderRequestPayload(br *openrtb_ext.RequestWrapper) +} diff --git a/hooks/hookstage/processedauctionrequest.go b/hooks/hookstage/processedauctionrequest.go index f420561310c..02638dccc20 100644 --- a/hooks/hookstage/processedauctionrequest.go +++ b/hooks/hookstage/processedauctionrequest.go @@ -2,7 +2,6 @@ package hookstage import ( "context" - "github.com/prebid/prebid-server/v2/openrtb_ext" ) @@ -28,3 +27,11 @@ type ProcessedAuctionRequest interface { type ProcessedAuctionRequestPayload struct { Request *openrtb_ext.RequestWrapper } + +func (parp *ProcessedAuctionRequestPayload) GetBidderRequestPayload() *openrtb_ext.RequestWrapper { + return parp.Request +} + +func (parp *ProcessedAuctionRequestPayload) SetBidderRequestPayload(br *openrtb_ext.RequestWrapper) { + parp.Request = br +} diff --git a/ortb/clone.go b/ortb/clone.go index c831aae21b5..2fe9be68f74 100644 --- a/ortb/clone.go +++ b/ortb/clone.go @@ -2,6 +2,7 @@ package ortb import ( "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/prebid/prebid-server/v2/util/sliceutil" ) @@ -399,3 +400,33 @@ func CloneDOOH(s *openrtb2.DOOH) *openrtb2.DOOH { return &c } + +// cloneBidderReq - clones bidder request and replaces req.User and req.Device and req.Source with new copies +func CloneBidderReq(req *openrtb2.BidRequest) *openrtb_ext.RequestWrapper { + if req == nil { + return nil + } + + // bidder request may be modified differently per bidder based on privacy configs + // new request should be created for each bidder request + // pointer fields like User and Device should be cloned and set back to the request copy + newReq := ptrutil.Clone(req) + + if req.User != nil { + userCopy := CloneUser(req.User) + newReq.User = userCopy + } + + if req.Device != nil { + deviceCopy := CloneDevice(req.Device) + newReq.Device = deviceCopy + } + + if req.Source != nil { + sourceCopy := CloneSource(req.Source) + newReq.Source = sourceCopy + } + + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: newReq} + return reqWrapper +} diff --git a/ortb/clone_test.go b/ortb/clone_test.go index 820c24397f4..1afc269240b 100644 --- a/ortb/clone_test.go +++ b/ortb/clone_test.go @@ -1127,6 +1127,58 @@ func TestCloneDOOH(t *testing.T) { }) } +func TestCloneBidderReq(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneBidderReq(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.BidRequest{} + result := CloneBidderReq(given) + assert.Equal(t, given, result.BidRequest) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + given := &openrtb2.BidRequest{ + ID: "anyID", + User: &openrtb2.User{ID: "testUserId"}, + Device: &openrtb2.Device{Carrier: "testCarrier"}, + Source: &openrtb2.Source{TID: "testTID"}, + } + result := CloneBidderReq(given) + assert.Equal(t, given, result.BidRequest) + assert.NotSame(t, given, result.BidRequest, "pointer") + assert.NotSame(t, given.User, result.User, "user") + assert.NotSame(t, given.Device, result.Device, "device") + assert.NotSame(t, given.Source, result.Source, "source") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.BidRequest{})), + []string{ + "Device", + "User", + "Source", + "Imp", + "Site", + "App", + "DOOH", + "WSeat", + "BSeat", + "Cur", + "WLang", + "WLangB", + "BCat", + "BAdv", + "BApp", + "Regs", + "Ext", + }) + }) +} + // discoverPointerFields returns the names of all fields of an object that are // pointers and would need to be cloned. This method is specific to types which can // appear within an OpenRTB data model object. From 25ce28ca836c0af589837cd4f7ab81b969e44baf Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Tue, 12 Dec 2023 09:24:00 +0530 Subject: [PATCH 131/138] Merge branch 'prebid_v2.0.2' into prebid_v2.1.0 --- .github/CODEOWNERS | 1 + .github/pull_request_template.md | 12 + .github/workflows/adapter-code-coverage.yml | 9 +- .github/workflows/cross-repo-issue.yml | 2 +- .github/workflows/validate-merge.yml | 9 +- .github/workflows/validate.yml | 4 + .gitignore | 3 + Makefile | 23 + README.md | 9 +- adapters/appnexus/appnexus.go | 5 +- .../video-same-adpodid-two-imps-same-pod.json | 5 +- adapters/bidder.go | 9 + adapters/conversant/conversant.go | 2 +- .../conversanttest/exemplary/banner.json | 2 +- .../conversanttest/exemplary/simple_app.json | 2 +- .../conversanttest/exemplary/video.json | 2 +- .../supplemental/server_badresponse.json | 2 +- .../supplemental/server_nocontent.json | 2 +- .../supplemental/server_unknownstatus.json | 2 +- .../supplemental/test_params.json | 8 +- adapters/infoawarebidder_ow_test.go | 194 + adapters/pubmatic/pubmatic.go | 182 +- adapters/pubmatic/pubmatic_ow.go | 76 + adapters/pubmatic/pubmatic_ow_test.go | 249 + adapters/pubmatic/pubmatic_test.go | 81 +- .../pubmatictest/exemplary/banner.json | 13 +- .../exemplary/video-rewarded.json | 175 + .../pubmatictest/params/race/banner.json | 15 + .../pubmatictest/params/race/video.json | 15 + .../pubmatictest/supplemental/app.json | 50 +- .../supplemental/banner-video.json | 129 + .../pubmatictest/supplemental/bidExtMeta.json | 179 + .../pubmatictest/supplemental/eid.json | 223 + .../supplemental/gptSlotNameInImpExt.json | 16 +- .../gptSlotNameInImpExtPbAdslot.json | 18 +- .../supplemental/multiformat.json | 135 + .../supplemental/pmZoneIDInKeywords.json | 2 +- .../supplemental/urlEncodedDCTR.json | 167 + adapters/spotx/params_test.go | 58 + adapters/spotx/spotx.go | 179 + adapters/spotx/spotx_test.go | 66 + adapters/vastbidder/bidder_macro.go | 1344 +++++ adapters/vastbidder/bidder_macro_test.go | 1763 +++++++ adapters/vastbidder/constant.go | 177 + adapters/vastbidder/ibidder_macro.go | 201 + adapters/vastbidder/itag_response_handler.go | 43 + adapters/vastbidder/macro_processor.go | 177 + adapters/vastbidder/macro_processor_test.go | 304 ++ adapters/vastbidder/mapper.go | 187 + adapters/vastbidder/sample_spotx_macro.go.bak | 28 + adapters/vastbidder/tagbidder.go | 87 + adapters/vastbidder/tagbidder_test.go | 150 + adapters/vastbidder/util.go | 134 + adapters/vastbidder/util_test.go | 195 + .../vastbidder/vast_tag_response_handler.go | 347 ++ .../vast_tag_response_handler_test.go | 387 ++ analytics/build/config_ow.go | 20 + analytics/build/config_ow_test.go | 67 + analytics/build/xyz1.txt-20231123 | 0 analytics/build/xyz1.txt-20231124 | 0 analytics/build/xyz1.txt-20231127 | 0 analytics/pubmatic/helper.go | 74 + analytics/pubmatic/helper_test.go | 220 + analytics/pubmatic/logger.go | 500 ++ analytics/pubmatic/logger_test.go | 4367 +++++++++++++++++ analytics/pubmatic/mhttp/http_util.go | 236 + analytics/pubmatic/mhttp/http_util_test.go | 162 + analytics/pubmatic/mhttp/mock/mock.go | 149 + analytics/pubmatic/models.go | 133 + analytics/pubmatic/pubmatic.go | 98 + analytics/pubmatic/pubmatic_test.go | 79 + analytics/pubmatic/record.go | 265 + analytics/pubmatic/record_test.go | 519 ++ config/account.go | 2 +- config/config.go | 41 +- config/config_test.go | 16 +- docs/developers/automated-tests.md | 2 +- docs/developers/code-reviews.md | 6 +- docs/developers/contributing.md | 6 +- endpoints/cookie_sync.go | 16 +- endpoints/cookie_sync_test.go | 7 + endpoints/events/vtrack_ow.go | 284 ++ endpoints/events/vtrack_ow_test.go | 636 +++ endpoints/events/vtrack_test.go | 1 - endpoints/openrtb2/amp_auction.go | 3 +- endpoints/openrtb2/amp_auction_test.go | 4 +- endpoints/openrtb2/auction.go | 24 +- endpoints/openrtb2/auction_ow.go | 141 + endpoints/openrtb2/auction_ow_test.go | 181 + endpoints/openrtb2/auction_test.go | 12 +- .../adslot_combination_generator.go | 586 +++ .../adslot_combination_generator_test.go | 240 + .../openrtb2/ctv/combination/combination.go | 71 + .../ctv/combination/combination_test.go | 38 + .../ctv/combination/test_input/TC_1.json | 4 + endpoints/openrtb2/ctv/constant/constant.go | 53 + .../ctv/impressions/by_duration_range_test.go | 177 + .../ctv/impressions/by_duration_ranges.go | 91 + endpoints/openrtb2/ctv/impressions/helper.go | 140 + .../ctv/impressions/impression_generator.go | 352 ++ .../openrtb2/ctv/impressions/impressions.go | 116 + .../ctv/impressions/impressions_test.go | 147 + .../ctv/impressions/maximize_for_duration.go | 25 + .../impressions/maximize_for_duration_test.go | 465 ++ .../ctv/impressions/min_max_algorithm.go | 191 + .../ctv/impressions/min_max_algorithm_test.go | 601 +++ .../ctv/impressions/testdata/input.go | 61 + .../ctv/impressions/testdata/output.go | 236 + .../ctv/response/adpod_generator copy.go.bak | 276 ++ .../openrtb2/ctv/response/adpod_generator.go | 397 ++ .../ctv/response/adpod_generator_test.go | 397 ++ .../ctv/response/adpod_generator_test.go.bak | 124 + endpoints/openrtb2/ctv/types/adpod_types.go | 64 + endpoints/openrtb2/ctv/util/util.go | 141 + endpoints/openrtb2/ctv/util/util_test.go | 342 ++ endpoints/openrtb2/ctv_auction.go | 1254 +++++ endpoints/openrtb2/ctv_auction_test.go | 836 ++++ .../custom-rates/valid/origbidcpmusd.json | 76 + endpoints/openrtb2/test_utils.go | 14 + endpoints/openrtb2/video_auction.go | 2 +- endpoints/openrtb2/video_auction_test.go | 4 +- errortypes/code.go | 7 + errortypes/errortypes.go | 70 + errortypes/errortypes_test.go | 177 + exchange/adapter_builders.go | 6 + exchange/auction.go | 16 +- exchange/auction_test.go | 14 + exchange/bidder.go | 66 +- exchange/bidder_test.go | 34 +- exchange/entities/entities.go | 4 + exchange/events.go | 27 +- exchange/events_test.go | 107 + exchange/exchange.go | 199 +- exchange/exchange_ow.go | 268 + exchange/exchange_ow_test.go | 1831 +++++++ exchange/exchange_test.go | 405 +- exchange/floors_ow.go | 23 + exchange/floors_ow_test.go | 157 + exchange/non_bid_reason.go | 4 + exchange/price_granularity.go | 3 +- exchange/price_granularity_test.go | 11 +- exchange/seat_non_bids.go | 12 + exchange/targeting.go | 9 + exchange/targeting_test.go | 1 + exchange/utils.go | 8 + exchange/utils_ow.go | 259 + exchange/utils_ow_test.go | 1049 ++++ floors/enforce.go | 35 +- floors/enforce_ow.go | 8 + floors/enforce_test.go | 114 +- floors/fetcher.go | 12 +- floors/fetcher_test.go | 30 +- floors/floors.go | 57 +- floors/floors_ow.go | 14 + floors/floors_ow_test.go | 43 + floors/floors_test.go | 258 +- floors/rule.go | 6 +- floors/rule_test.go | 8 + gdpr/vendorlist-fetching.go | 5 +- gdpr/vendorlist-scheduler.go | 132 + gdpr/vendorlist-scheduler_test.go | 212 + go.mod | 32 +- go.sum | 30 +- hooks/empty_plan.go | 4 + hooks/hookexecution/context.go | 2 + hooks/hookexecution/executor.go | 35 + hooks/hookexecution/executor_test.go | 11 + hooks/hookexecution/mocks_test.go | 17 + hooks/hookstage/invocation.go | 1 + .../processedbeforerequestvalidation.go | 22 + hooks/plan.go | 18 +- hooks/plan_test.go | 10 + hooks/repo.go | 13 + main.go | 7 +- main_test.go | 2 +- metrics/config/metrics.go | 119 +- metrics/config/metrics_ow.go | 90 + metrics/config/metrics_ow_test.go | 82 + metrics/config/metrics_test.go | 4 +- metrics/go_metrics.go | 106 +- metrics/go_metrics_ow.go | 46 + metrics/go_metrics_test.go | 63 +- metrics/metrics.go | 55 +- metrics/metrics_mock.go | 27 +- metrics/metrics_mock_ow.go | 47 + metrics/metrics_ow.go | 11 + metrics/prometheus/prometheus.go | 52 +- metrics/prometheus/prometheus_ow.go | 260 + metrics/prometheus/prometheus_ow_test.go | 170 + metrics/prometheus/prometheus_test.go | 363 +- metrics/pubmatic_stats/stats.go | 11 + metrics/pubmatic_stats/stats_test.go | 11 + modules/builder.go | 6 + modules/helpers.go | 6 + modules/moduledeps/deps.go | 8 +- modules/prebid/ortb2blocking/README.md | 4 +- modules/pubmatic/openwrap/abtest.go | 119 + modules/pubmatic/openwrap/abtest_test.go | 606 +++ .../openwrap/adapters/bidder_alias.go | 32 + modules/pubmatic/openwrap/adapters/bidders.go | 604 +++ modules/pubmatic/openwrap/adapters/builder.go | 75 + .../pubmatic/openwrap/adapters/constant.go | 32 + .../pubmatic/openwrap/adapters/converter.go | 38 + .../openwrap/adapters/default_bidder.go | 189 + .../adapters/default_bidder_parameter.go | 210 + .../pubmatic/openwrap/adapters/pubmatic.go | 1 + .../adapters/tests/hybrid_bidders.json | 352 ++ .../openwrap/adapters/tests/s2s_bidders.json | 18 + modules/pubmatic/openwrap/adapters/util.go | 177 + .../pubmatic/openwrap/adapters/vastbidder.go | 83 + modules/pubmatic/openwrap/adapterthrottle.go | 52 + .../pubmatic/openwrap/adapterthrottle_test.go | 207 + modules/pubmatic/openwrap/adunitconfig/app.go | 108 + .../pubmatic/openwrap/adunitconfig/banner.go | 65 + .../openwrap/adunitconfig/banner_test.go | 373 ++ .../pubmatic/openwrap/adunitconfig/common.go | 59 + .../openwrap/adunitconfig/common_test.go | 183 + .../pubmatic/openwrap/adunitconfig/device.go | 34 + .../pubmatic/openwrap/adunitconfig/floors.go | 30 + .../pubmatic/openwrap/adunitconfig/regex.go | 24 + .../openwrap/adunitconfig/regex_cache.go | 64 + .../openwrap/adunitconfig/regex_cache_test.go | 79 + .../openwrap/adunitconfig/regex_test.go | 109 + .../pubmatic/openwrap/adunitconfig/utils.go | 103 + .../openwrap/adunitconfig/utils_test.go | 291 ++ .../pubmatic/openwrap/adunitconfig/video.go | 87 + .../openwrap/adunitconfig/video_test.go | 434 ++ .../openwrap/allprocessedbidresponsehook.go | 43 + .../allprocessedbidresponsehook_test.go | 69 + .../pubmatic/openwrap/auctionresponsehook.go | 424 ++ .../openwrap/auctionresponsehook_test.go | 1291 +++++ .../pubmatic/openwrap/beforevalidationhook.go | 975 ++++ .../openwrap/beforevalidationhook_test.go | 2972 +++++++++++ .../pubmatic/openwrap/bidderparams/common.go | 178 + .../openwrap/bidderparams/common_test.go | 615 +++ .../pubmatic/openwrap/bidderparams/others.go | 58 + .../openwrap/bidderparams/pubmatic.go | 136 + .../openwrap/bidderparams/pubmatic_test.go | 877 ++++ .../pubmatic/openwrap/bidderparams/vast.go | 174 + .../openwrap/bidderparams/vast_test.go | 516 ++ modules/pubmatic/openwrap/bidders.go | 21 + modules/pubmatic/openwrap/cache/cache.go | 23 + .../openwrap/cache/gocache/adunit_config.go | 44 + .../cache/gocache/adunit_config_test.go | 406 ++ .../cache/gocache/fullscreenclickability.go | 32 + .../gocache/fullscreenclickability_test.go | 176 + .../openwrap/cache/gocache/gocache.go | 66 + .../openwrap/cache/gocache/gocache_test.go | 220 + .../openwrap/cache/gocache/partner_config.go | 99 + .../cache/gocache/partner_config_test.go | 423 ++ .../openwrap/cache/gocache/slot_mappings.go | 106 + .../cache/gocache/slot_mappings_test.go | 579 +++ .../pubmatic/openwrap/cache/gocache/sync.go | 19 + .../openwrap/cache/gocache/sync_test.go | 89 + .../cache/gocache/tracking_beacon_first.go | 12 + .../gocache/tracking_beacon_first_test.go | 42 + .../pubmatic/openwrap/cache/gocache/util.go | 25 + .../openwrap/cache/gocache/util_test.go | 176 + .../openwrap/cache/gocache/vast_tags.go | 33 + .../openwrap/cache/gocache/vast_tags_test.go | 226 + modules/pubmatic/openwrap/cache/mock/mock.go | 180 + modules/pubmatic/openwrap/config/config.go | 94 + .../openwrap/contenttransperencyobject.go | 59 + .../pubmatic/openwrap/database/database.go | 18 + .../pubmatic/openwrap/database/mock/mock.go | 171 + .../openwrap/database/mock_driver/mock.go | 208 + .../openwrap/database/mysql/adunit_config.go | 58 + .../database/mysql/adunit_config_test.go | 261 + .../database/mysql/fullscreenclickability.go | 50 + .../mysql/fullscreenclickability_test.go | 188 + .../pubmatic/openwrap/database/mysql/mysql.go | 24 + .../openwrap/database/mysql/mysql_test.go | 40 + .../openwrap/database/mysql/partner_config.go | 92 + .../database/mysql/partner_config_test.go | 665 +++ .../openwrap/database/mysql/queries.go | 9 + .../openwrap/database/mysql/slot_mapping.go | 99 + .../database/mysql/slot_mapping_test.go | 417 ++ .../database/mysql/tracking_beacon_first.go | 40 + .../mysql/tracking_beacon_first_test.go | 167 + .../openwrap/database/mysql/vasttags.go | 35 + .../openwrap/database/mysql/vasttags_test.go | 117 + modules/pubmatic/openwrap/defaultbids.go | 205 + modules/pubmatic/openwrap/defaultbids_test.go | 87 + modules/pubmatic/openwrap/device.go | 48 + .../endpoints/legacy/openrtb/v25/v25.go | 1 + .../endpoints/legacy/openrtb/v25/video.go | 716 +++ modules/pubmatic/openwrap/entrypointhook.go | 171 + .../pubmatic/openwrap/entrypointhook_test.go | 531 ++ modules/pubmatic/openwrap/floors.go | 53 + modules/pubmatic/openwrap/floors_test.go | 358 ++ .../fullscreenclickability.go | 103 + .../fullscreenclickability_test.go | 297 ++ .../fullscreenclickability/reloader.go | 37 + .../fullscreenclickability/reloader_test.go | 58 + modules/pubmatic/openwrap/logger.go | 51 + modules/pubmatic/openwrap/marketplace.go | 37 + modules/pubmatic/openwrap/marketplace_test.go | 143 + .../pubmatic/openwrap/matchedimpression.go | 42 + .../openwrap/metrics/config/metrics.go | 447 ++ .../openwrap/metrics/config/metrics_test.go | 282 ++ modules/pubmatic/openwrap/metrics/metrics.go | 75 + .../pubmatic/openwrap/metrics/metrics_sshb.go | 25 + .../pubmatic/openwrap/metrics/mock/mock.go | 684 +++ .../openwrap/metrics/prometheus/prometheus.go | 487 ++ .../metrics/prometheus/prometheus_sshb.go | 272 + .../prometheus/prometheus_sshb_test.go | 389 ++ .../metrics/prometheus/prometheus_test.go | 391 ++ .../pubmatic/openwrap/metrics/stats/client.go | 168 + .../openwrap/metrics/stats/client_config.go | 81 + .../metrics/stats/client_config_test.go | 159 + .../openwrap/metrics/stats/client_test.go | 439 ++ .../pubmatic/openwrap/metrics/stats/config.go | 18 + .../openwrap/metrics/stats/constants.go | 225 + .../pubmatic/openwrap/metrics/stats/init.go | 265 + .../openwrap/metrics/stats/init_test.go | 175 + .../openwrap/metrics/stats/mock/mock.go | 86 + .../openwrap/metrics/stats/tcp_stats.go | 341 ++ .../openwrap/metrics/stats/tcp_stats_test.go | 1120 +++++ modules/pubmatic/openwrap/models/adcom.go | 130 + .../models/adunitconfig/adunitconfig.go | 84 + modules/pubmatic/openwrap/models/amp.go | 19 + modules/pubmatic/openwrap/models/bidders.go | 24 + modules/pubmatic/openwrap/models/constants.go | 492 ++ modules/pubmatic/openwrap/models/db.go | 55 + modules/pubmatic/openwrap/models/device.go | 63 + modules/pubmatic/openwrap/models/gocommon.go | 393 ++ modules/pubmatic/openwrap/models/iso6391.go | 203 + modules/pubmatic/openwrap/models/nbr/codes.go | 18 + modules/pubmatic/openwrap/models/openwrap.go | 174 + .../pubmatic/openwrap/models/openwrap_test.go | 45 + modules/pubmatic/openwrap/models/ortb.go | 72 + modules/pubmatic/openwrap/models/reponse.go | 88 + modules/pubmatic/openwrap/models/request.go | 114 + modules/pubmatic/openwrap/models/source.go | 9 + modules/pubmatic/openwrap/models/tracker.go | 79 + modules/pubmatic/openwrap/models/tracking.go | 105 + modules/pubmatic/openwrap/models/utils.go | 415 ++ .../pubmatic/openwrap/models/utils_legacy.go | 17 + .../openwrap/models/utils_legacy_test.go | 82 + .../pubmatic/openwrap/models/utils_test.go | 1333 +++++ modules/pubmatic/openwrap/models/video.go | 34 + modules/pubmatic/openwrap/module.go | 93 + modules/pubmatic/openwrap/nonbids.go | 99 + modules/pubmatic/openwrap/nonbids_test.go | 829 ++++ modules/pubmatic/openwrap/openwrap.go | 109 + modules/pubmatic/openwrap/openwrap_sshb.go | 38 + .../pubmatic/openwrap/price_granularity.go | 56 + .../openwrap/price_granularity_test.go | 256 + .../pubmatic/openwrap/processedauctionhook.go | 38 + modules/pubmatic/openwrap/profiledata.go | 40 + modules/pubmatic/openwrap/profiledata_test.go | 255 + modules/pubmatic/openwrap/schain.go | 30 + modules/pubmatic/openwrap/targeting.go | 121 + modules/pubmatic/openwrap/tbf/tbf.go | 133 + modules/pubmatic/openwrap/tbf/tbf_test.go | 233 + modules/pubmatic/openwrap/tracker/banner.go | 59 + .../pubmatic/openwrap/tracker/banner_test.go | 301 ++ modules/pubmatic/openwrap/tracker/create.go | 334 ++ .../pubmatic/openwrap/tracker/create_test.go | 846 ++++ modules/pubmatic/openwrap/tracker/inject.go | 79 + .../pubmatic/openwrap/tracker/inject_test.go | 688 +++ modules/pubmatic/openwrap/tracker/models.go | 8 + .../pubmatic/openwrap/tracker/models_test.go | 1 + modules/pubmatic/openwrap/tracker/native.go | 77 + .../pubmatic/openwrap/tracker/native_test.go | 331 ++ modules/pubmatic/openwrap/tracker/tracker.go | 50 + .../pubmatic/openwrap/tracker/tracker_test.go | 87 + modules/pubmatic/openwrap/tracker/video.go | 157 + .../pubmatic/openwrap/tracker/video_test.go | 565 +++ modules/pubmatic/openwrap/util.go | 301 ++ modules/pubmatic/openwrap/util_test.go | 947 ++++ modules/pubmatic/openwrap/utils/bid.go | 17 + modules/pubmatic/openwrap/utils/bid_test.go | 125 + modules/pubmatic/vastunwrap/README.md | 6 + modules/pubmatic/vastunwrap/constant.go | 17 + modules/pubmatic/vastunwrap/entryhook.go | 34 + modules/pubmatic/vastunwrap/entryhook_test.go | 67 + .../vastunwrap/hook_raw_bidder_response.go | 44 + .../hook_raw_bidder_response_test.go | 292 ++ modules/pubmatic/vastunwrap/models/request.go | 6 + modules/pubmatic/vastunwrap/module.go | 75 + modules/pubmatic/vastunwrap/module_test.go | 309 ++ modules/pubmatic/vastunwrap/respwriter.go | 107 + modules/pubmatic/vastunwrap/stats/metrics.go | 109 + .../pubmatic/vastunwrap/stats/metrics_test.go | 107 + .../pubmatic/vastunwrap/stats/mock/mock.go | 70 + modules/pubmatic/vastunwrap/unwrap_service.go | 52 + .../vastunwrap/unwrap_service_test.go | 176 + openrtb_ext/adpod.go | 326 ++ openrtb_ext/adpod_test.go | 315 ++ openrtb_ext/bid.go | 8 +- openrtb_ext/bidders.go | 6 + openrtb_ext/device.go | 8 + openrtb_ext/floors.go | 1 + openrtb_ext/imp_pubmatic.go | 16 +- openrtb_ext/imp_spotx.go | 10 + openrtb_ext/imp_vastbidder.go | 18 + openrtb_ext/request.go | 31 + openrtb_ext/request_wrapper.go | 8 +- openrtb_ext/response.go | 28 +- openrtb_ext/response_ow.go | 7 + openrtb_ext/supplyChain.go | 57 + openrtb_ext/supplyChain_test.go | 211 + openrtb_ext/user.go | 3 + router/router.go | 51 +- router/router_ow.go | 37 + router/router_sshb.go | 141 + router/router_test.go | 1 + scripts/coverage.sh | 48 +- scripts/upgrade-pbs.sh | 262 + static/bidder-info/spotx.yaml | 10 + static/bidder-info/vastbidder.yaml | 9 + static/bidder-params/spotx.json | 39 + static/bidder-params/vastbidder.json | 27 + usersync/cookie.go | 3 +- util/boolutil/boolutil.go | 6 + version/xprebidheader.go | 2 +- version/xprebidheader_test.go | 20 +- 418 files changed, 73205 insertions(+), 465 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/pull_request_template.md create mode 100644 adapters/infoawarebidder_ow_test.go create mode 100644 adapters/pubmatic/pubmatic_ow.go create mode 100644 adapters/pubmatic/pubmatic_ow_test.go create mode 100644 adapters/pubmatic/pubmatictest/exemplary/video-rewarded.json create mode 100644 adapters/pubmatic/pubmatictest/params/race/banner.json create mode 100644 adapters/pubmatic/pubmatictest/params/race/video.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/banner-video.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/bidExtMeta.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/eid.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/multiformat.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/urlEncodedDCTR.json create mode 100644 adapters/spotx/params_test.go create mode 100644 adapters/spotx/spotx.go create mode 100644 adapters/spotx/spotx_test.go create mode 100644 adapters/vastbidder/bidder_macro.go create mode 100644 adapters/vastbidder/bidder_macro_test.go create mode 100644 adapters/vastbidder/constant.go create mode 100644 adapters/vastbidder/ibidder_macro.go create mode 100644 adapters/vastbidder/itag_response_handler.go create mode 100644 adapters/vastbidder/macro_processor.go create mode 100644 adapters/vastbidder/macro_processor_test.go create mode 100644 adapters/vastbidder/mapper.go create mode 100644 adapters/vastbidder/sample_spotx_macro.go.bak create mode 100644 adapters/vastbidder/tagbidder.go create mode 100644 adapters/vastbidder/tagbidder_test.go create mode 100644 adapters/vastbidder/util.go create mode 100644 adapters/vastbidder/util_test.go create mode 100644 adapters/vastbidder/vast_tag_response_handler.go create mode 100644 adapters/vastbidder/vast_tag_response_handler_test.go create mode 100644 analytics/build/config_ow.go create mode 100644 analytics/build/config_ow_test.go create mode 100644 analytics/build/xyz1.txt-20231123 create mode 100644 analytics/build/xyz1.txt-20231124 create mode 100644 analytics/build/xyz1.txt-20231127 create mode 100644 analytics/pubmatic/helper.go create mode 100644 analytics/pubmatic/helper_test.go create mode 100644 analytics/pubmatic/logger.go create mode 100644 analytics/pubmatic/logger_test.go create mode 100644 analytics/pubmatic/mhttp/http_util.go create mode 100644 analytics/pubmatic/mhttp/http_util_test.go create mode 100644 analytics/pubmatic/mhttp/mock/mock.go create mode 100644 analytics/pubmatic/models.go create mode 100644 analytics/pubmatic/pubmatic.go create mode 100644 analytics/pubmatic/pubmatic_test.go create mode 100644 analytics/pubmatic/record.go create mode 100644 analytics/pubmatic/record_test.go create mode 100644 endpoints/events/vtrack_ow.go create mode 100644 endpoints/events/vtrack_ow_test.go create mode 100644 endpoints/openrtb2/auction_ow.go create mode 100644 endpoints/openrtb2/auction_ow_test.go create mode 100644 endpoints/openrtb2/ctv/combination/adslot_combination_generator.go create mode 100644 endpoints/openrtb2/ctv/combination/adslot_combination_generator_test.go create mode 100644 endpoints/openrtb2/ctv/combination/combination.go create mode 100644 endpoints/openrtb2/ctv/combination/combination_test.go create mode 100644 endpoints/openrtb2/ctv/combination/test_input/TC_1.json create mode 100644 endpoints/openrtb2/ctv/constant/constant.go create mode 100644 endpoints/openrtb2/ctv/impressions/by_duration_range_test.go create mode 100644 endpoints/openrtb2/ctv/impressions/by_duration_ranges.go create mode 100644 endpoints/openrtb2/ctv/impressions/helper.go create mode 100644 endpoints/openrtb2/ctv/impressions/impression_generator.go create mode 100644 endpoints/openrtb2/ctv/impressions/impressions.go create mode 100644 endpoints/openrtb2/ctv/impressions/impressions_test.go create mode 100644 endpoints/openrtb2/ctv/impressions/maximize_for_duration.go create mode 100644 endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go create mode 100644 endpoints/openrtb2/ctv/impressions/min_max_algorithm.go create mode 100644 endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go create mode 100644 endpoints/openrtb2/ctv/impressions/testdata/input.go create mode 100644 endpoints/openrtb2/ctv/impressions/testdata/output.go create mode 100644 endpoints/openrtb2/ctv/response/adpod_generator copy.go.bak create mode 100644 endpoints/openrtb2/ctv/response/adpod_generator.go create mode 100644 endpoints/openrtb2/ctv/response/adpod_generator_test.go create mode 100644 endpoints/openrtb2/ctv/response/adpod_generator_test.go.bak create mode 100644 endpoints/openrtb2/ctv/types/adpod_types.go create mode 100644 endpoints/openrtb2/ctv/util/util.go create mode 100644 endpoints/openrtb2/ctv/util/util_test.go create mode 100644 endpoints/openrtb2/ctv_auction.go create mode 100644 endpoints/openrtb2/ctv_auction_test.go create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/origbidcpmusd.json create mode 100644 errortypes/errortypes_test.go create mode 100644 exchange/exchange_ow.go create mode 100644 exchange/exchange_ow_test.go create mode 100644 exchange/floors_ow.go create mode 100644 exchange/floors_ow_test.go create mode 100644 exchange/utils_ow.go create mode 100644 exchange/utils_ow_test.go create mode 100644 floors/enforce_ow.go create mode 100644 floors/floors_ow.go create mode 100644 floors/floors_ow_test.go create mode 100644 gdpr/vendorlist-scheduler.go create mode 100644 gdpr/vendorlist-scheduler_test.go create mode 100644 hooks/hookstage/processedbeforerequestvalidation.go create mode 100644 metrics/config/metrics_ow.go create mode 100644 metrics/config/metrics_ow_test.go create mode 100644 metrics/go_metrics_ow.go create mode 100644 metrics/metrics_mock_ow.go create mode 100644 metrics/metrics_ow.go create mode 100644 metrics/prometheus/prometheus_ow.go create mode 100644 metrics/prometheus/prometheus_ow_test.go create mode 100644 metrics/pubmatic_stats/stats.go create mode 100644 metrics/pubmatic_stats/stats_test.go create mode 100644 modules/pubmatic/openwrap/abtest.go create mode 100644 modules/pubmatic/openwrap/abtest_test.go create mode 100644 modules/pubmatic/openwrap/adapters/bidder_alias.go create mode 100644 modules/pubmatic/openwrap/adapters/bidders.go create mode 100644 modules/pubmatic/openwrap/adapters/builder.go create mode 100644 modules/pubmatic/openwrap/adapters/constant.go create mode 100644 modules/pubmatic/openwrap/adapters/converter.go create mode 100644 modules/pubmatic/openwrap/adapters/default_bidder.go create mode 100644 modules/pubmatic/openwrap/adapters/default_bidder_parameter.go create mode 100644 modules/pubmatic/openwrap/adapters/pubmatic.go create mode 100644 modules/pubmatic/openwrap/adapters/tests/hybrid_bidders.json create mode 100644 modules/pubmatic/openwrap/adapters/tests/s2s_bidders.json create mode 100644 modules/pubmatic/openwrap/adapters/util.go create mode 100644 modules/pubmatic/openwrap/adapters/vastbidder.go create mode 100644 modules/pubmatic/openwrap/adapterthrottle.go create mode 100644 modules/pubmatic/openwrap/adapterthrottle_test.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/app.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/banner.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/banner_test.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/common.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/common_test.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/device.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/floors.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/regex.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/regex_cache.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/regex_cache_test.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/regex_test.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/utils.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/utils_test.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/video.go create mode 100644 modules/pubmatic/openwrap/adunitconfig/video_test.go create mode 100644 modules/pubmatic/openwrap/allprocessedbidresponsehook.go create mode 100644 modules/pubmatic/openwrap/allprocessedbidresponsehook_test.go create mode 100644 modules/pubmatic/openwrap/auctionresponsehook.go create mode 100644 modules/pubmatic/openwrap/auctionresponsehook_test.go create mode 100644 modules/pubmatic/openwrap/beforevalidationhook.go create mode 100644 modules/pubmatic/openwrap/beforevalidationhook_test.go create mode 100644 modules/pubmatic/openwrap/bidderparams/common.go create mode 100644 modules/pubmatic/openwrap/bidderparams/common_test.go create mode 100644 modules/pubmatic/openwrap/bidderparams/others.go create mode 100644 modules/pubmatic/openwrap/bidderparams/pubmatic.go create mode 100644 modules/pubmatic/openwrap/bidderparams/pubmatic_test.go create mode 100644 modules/pubmatic/openwrap/bidderparams/vast.go create mode 100644 modules/pubmatic/openwrap/bidderparams/vast_test.go create mode 100644 modules/pubmatic/openwrap/bidders.go create mode 100644 modules/pubmatic/openwrap/cache/cache.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/adunit_config.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/adunit_config_test.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/fullscreenclickability.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/fullscreenclickability_test.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/gocache.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/gocache_test.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/partner_config.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/partner_config_test.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/slot_mappings.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/slot_mappings_test.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/sync.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/sync_test.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/tracking_beacon_first.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/tracking_beacon_first_test.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/util.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/util_test.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/vast_tags.go create mode 100644 modules/pubmatic/openwrap/cache/gocache/vast_tags_test.go create mode 100644 modules/pubmatic/openwrap/cache/mock/mock.go create mode 100755 modules/pubmatic/openwrap/config/config.go create mode 100644 modules/pubmatic/openwrap/contenttransperencyobject.go create mode 100644 modules/pubmatic/openwrap/database/database.go create mode 100644 modules/pubmatic/openwrap/database/mock/mock.go create mode 100644 modules/pubmatic/openwrap/database/mock_driver/mock.go create mode 100644 modules/pubmatic/openwrap/database/mysql/adunit_config.go create mode 100644 modules/pubmatic/openwrap/database/mysql/adunit_config_test.go create mode 100644 modules/pubmatic/openwrap/database/mysql/fullscreenclickability.go create mode 100644 modules/pubmatic/openwrap/database/mysql/fullscreenclickability_test.go create mode 100644 modules/pubmatic/openwrap/database/mysql/mysql.go create mode 100644 modules/pubmatic/openwrap/database/mysql/mysql_test.go create mode 100644 modules/pubmatic/openwrap/database/mysql/partner_config.go create mode 100644 modules/pubmatic/openwrap/database/mysql/partner_config_test.go create mode 100644 modules/pubmatic/openwrap/database/mysql/queries.go create mode 100644 modules/pubmatic/openwrap/database/mysql/slot_mapping.go create mode 100644 modules/pubmatic/openwrap/database/mysql/slot_mapping_test.go create mode 100644 modules/pubmatic/openwrap/database/mysql/tracking_beacon_first.go create mode 100644 modules/pubmatic/openwrap/database/mysql/tracking_beacon_first_test.go create mode 100644 modules/pubmatic/openwrap/database/mysql/vasttags.go create mode 100644 modules/pubmatic/openwrap/database/mysql/vasttags_test.go create mode 100644 modules/pubmatic/openwrap/defaultbids.go create mode 100644 modules/pubmatic/openwrap/defaultbids_test.go create mode 100644 modules/pubmatic/openwrap/device.go create mode 100644 modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25/v25.go create mode 100644 modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25/video.go create mode 100644 modules/pubmatic/openwrap/entrypointhook.go create mode 100644 modules/pubmatic/openwrap/entrypointhook_test.go create mode 100644 modules/pubmatic/openwrap/floors.go create mode 100644 modules/pubmatic/openwrap/floors_test.go create mode 100644 modules/pubmatic/openwrap/fullscreenclickability/fullscreenclickability.go create mode 100644 modules/pubmatic/openwrap/fullscreenclickability/fullscreenclickability_test.go create mode 100644 modules/pubmatic/openwrap/fullscreenclickability/reloader.go create mode 100644 modules/pubmatic/openwrap/fullscreenclickability/reloader_test.go create mode 100644 modules/pubmatic/openwrap/logger.go create mode 100644 modules/pubmatic/openwrap/marketplace.go create mode 100644 modules/pubmatic/openwrap/marketplace_test.go create mode 100644 modules/pubmatic/openwrap/matchedimpression.go create mode 100644 modules/pubmatic/openwrap/metrics/config/metrics.go create mode 100644 modules/pubmatic/openwrap/metrics/config/metrics_test.go create mode 100644 modules/pubmatic/openwrap/metrics/metrics.go create mode 100644 modules/pubmatic/openwrap/metrics/metrics_sshb.go create mode 100644 modules/pubmatic/openwrap/metrics/mock/mock.go create mode 100644 modules/pubmatic/openwrap/metrics/prometheus/prometheus.go create mode 100644 modules/pubmatic/openwrap/metrics/prometheus/prometheus_sshb.go create mode 100644 modules/pubmatic/openwrap/metrics/prometheus/prometheus_sshb_test.go create mode 100644 modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go create mode 100644 modules/pubmatic/openwrap/metrics/stats/client.go create mode 100644 modules/pubmatic/openwrap/metrics/stats/client_config.go create mode 100644 modules/pubmatic/openwrap/metrics/stats/client_config_test.go create mode 100644 modules/pubmatic/openwrap/metrics/stats/client_test.go create mode 100644 modules/pubmatic/openwrap/metrics/stats/config.go create mode 100644 modules/pubmatic/openwrap/metrics/stats/constants.go create mode 100644 modules/pubmatic/openwrap/metrics/stats/init.go create mode 100644 modules/pubmatic/openwrap/metrics/stats/init_test.go create mode 100644 modules/pubmatic/openwrap/metrics/stats/mock/mock.go create mode 100644 modules/pubmatic/openwrap/metrics/stats/tcp_stats.go create mode 100644 modules/pubmatic/openwrap/metrics/stats/tcp_stats_test.go create mode 100644 modules/pubmatic/openwrap/models/adcom.go create mode 100644 modules/pubmatic/openwrap/models/adunitconfig/adunitconfig.go create mode 100644 modules/pubmatic/openwrap/models/amp.go create mode 100644 modules/pubmatic/openwrap/models/bidders.go create mode 100755 modules/pubmatic/openwrap/models/constants.go create mode 100644 modules/pubmatic/openwrap/models/db.go create mode 100644 modules/pubmatic/openwrap/models/device.go create mode 100644 modules/pubmatic/openwrap/models/gocommon.go create mode 100644 modules/pubmatic/openwrap/models/iso6391.go create mode 100644 modules/pubmatic/openwrap/models/nbr/codes.go create mode 100644 modules/pubmatic/openwrap/models/openwrap.go create mode 100644 modules/pubmatic/openwrap/models/openwrap_test.go create mode 100644 modules/pubmatic/openwrap/models/ortb.go create mode 100644 modules/pubmatic/openwrap/models/reponse.go create mode 100644 modules/pubmatic/openwrap/models/request.go create mode 100644 modules/pubmatic/openwrap/models/source.go create mode 100644 modules/pubmatic/openwrap/models/tracker.go create mode 100644 modules/pubmatic/openwrap/models/tracking.go create mode 100644 modules/pubmatic/openwrap/models/utils.go create mode 100644 modules/pubmatic/openwrap/models/utils_legacy.go create mode 100644 modules/pubmatic/openwrap/models/utils_legacy_test.go create mode 100644 modules/pubmatic/openwrap/models/utils_test.go create mode 100644 modules/pubmatic/openwrap/models/video.go create mode 100644 modules/pubmatic/openwrap/module.go create mode 100644 modules/pubmatic/openwrap/nonbids.go create mode 100644 modules/pubmatic/openwrap/nonbids_test.go create mode 100644 modules/pubmatic/openwrap/openwrap.go create mode 100644 modules/pubmatic/openwrap/openwrap_sshb.go create mode 100644 modules/pubmatic/openwrap/price_granularity.go create mode 100644 modules/pubmatic/openwrap/price_granularity_test.go create mode 100644 modules/pubmatic/openwrap/processedauctionhook.go create mode 100644 modules/pubmatic/openwrap/profiledata.go create mode 100644 modules/pubmatic/openwrap/profiledata_test.go create mode 100644 modules/pubmatic/openwrap/schain.go create mode 100644 modules/pubmatic/openwrap/targeting.go create mode 100644 modules/pubmatic/openwrap/tbf/tbf.go create mode 100644 modules/pubmatic/openwrap/tbf/tbf_test.go create mode 100644 modules/pubmatic/openwrap/tracker/banner.go create mode 100644 modules/pubmatic/openwrap/tracker/banner_test.go create mode 100644 modules/pubmatic/openwrap/tracker/create.go create mode 100644 modules/pubmatic/openwrap/tracker/create_test.go create mode 100644 modules/pubmatic/openwrap/tracker/inject.go create mode 100644 modules/pubmatic/openwrap/tracker/inject_test.go create mode 100644 modules/pubmatic/openwrap/tracker/models.go create mode 100644 modules/pubmatic/openwrap/tracker/models_test.go create mode 100644 modules/pubmatic/openwrap/tracker/native.go create mode 100644 modules/pubmatic/openwrap/tracker/native_test.go create mode 100644 modules/pubmatic/openwrap/tracker/tracker.go create mode 100644 modules/pubmatic/openwrap/tracker/tracker_test.go create mode 100644 modules/pubmatic/openwrap/tracker/video.go create mode 100644 modules/pubmatic/openwrap/tracker/video_test.go create mode 100644 modules/pubmatic/openwrap/util.go create mode 100644 modules/pubmatic/openwrap/util_test.go create mode 100644 modules/pubmatic/openwrap/utils/bid.go create mode 100644 modules/pubmatic/openwrap/utils/bid_test.go create mode 100644 modules/pubmatic/vastunwrap/README.md create mode 100644 modules/pubmatic/vastunwrap/constant.go create mode 100644 modules/pubmatic/vastunwrap/entryhook.go create mode 100644 modules/pubmatic/vastunwrap/entryhook_test.go create mode 100644 modules/pubmatic/vastunwrap/hook_raw_bidder_response.go create mode 100644 modules/pubmatic/vastunwrap/hook_raw_bidder_response_test.go create mode 100644 modules/pubmatic/vastunwrap/models/request.go create mode 100644 modules/pubmatic/vastunwrap/module.go create mode 100644 modules/pubmatic/vastunwrap/module_test.go create mode 100644 modules/pubmatic/vastunwrap/respwriter.go create mode 100644 modules/pubmatic/vastunwrap/stats/metrics.go create mode 100644 modules/pubmatic/vastunwrap/stats/metrics_test.go create mode 100644 modules/pubmatic/vastunwrap/stats/mock/mock.go create mode 100644 modules/pubmatic/vastunwrap/unwrap_service.go create mode 100644 modules/pubmatic/vastunwrap/unwrap_service_test.go create mode 100644 openrtb_ext/adpod.go create mode 100644 openrtb_ext/adpod_test.go create mode 100644 openrtb_ext/imp_spotx.go create mode 100644 openrtb_ext/imp_vastbidder.go create mode 100644 openrtb_ext/response_ow.go create mode 100644 router/router_ow.go create mode 100644 router/router_sshb.go create mode 100755 scripts/upgrade-pbs.sh create mode 100644 static/bidder-info/spotx.yaml create mode 100644 static/bidder-info/vastbidder.yaml create mode 100644 static/bidder-params/spotx.json create mode 100644 static/bidder-params/vastbidder.json create mode 100644 util/boolutil/boolutil.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..0ea4f4c4318 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @viral-vala @shriprasad-marathe @ganesh-salpure diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000000..d124f544bd2 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,12 @@ +# Description + +Please add change description or link to ticket, docs, etc. + +# Checklist: + +- [ ] PR commit list is unique (rebase/pull with the origin branch to keep master clean). +- [ ] JIRA number is added in the PR title and the commit message. +- [ ] Updated the `header-bidding` repo with appropiate commit id. +- [ ] Documented the new changes. + +For Prebid upgrade, refer: https://inside.pubmatic.com:8443/confluence/display/Products/Prebid-server+upgrade diff --git a/.github/workflows/adapter-code-coverage.yml b/.github/workflows/adapter-code-coverage.yml index b4c3f0745d6..66f11b32033 100644 --- a/.github/workflows/adapter-code-coverage.yml +++ b/.github/workflows/adapter-code-coverage.yml @@ -43,6 +43,8 @@ jobs: id: run_coverage if: steps.get_directories.outputs.result != '' run: | + git config --global url."https://${USERNAME}:${TOKEN}@git.pubmatic.com".insteadOf "https://git.pubmatic.com" + directories=$(echo '${{ steps.get_directories.outputs.result }}' | jq -r '.[]') go mod download @@ -65,13 +67,18 @@ jobs: # remove pull request branch files cd .. rm -f -r ./* + env: + GO111MODULE: "on" + GOPRIVATE: "git.pubmatic.com/PubMatic/*" + TOKEN: ${{ secrets.PM_OPENWRAP_CICD_PASSWORD }} + USERNAME: ${{ secrets.PM_OPENWRAP_CICD_USERNAME }} - name: Checkout coverage-preview branch uses: actions/checkout@v3 with: fetch-depth: 0 ref: coverage-preview - repository: prebid/prebid-server + repository: PubMatic-OpenWrap/prebid-server - name: Commit coverage files to coverage-preview branch if: steps.run_coverage.outputs.coverage_dir != '' diff --git a/.github/workflows/cross-repo-issue.yml b/.github/workflows/cross-repo-issue.yml index 2bea44a301c..fd479cd2ad9 100644 --- a/.github/workflows/cross-repo-issue.yml +++ b/.github/workflows/cross-repo-issue.yml @@ -4,7 +4,7 @@ on: pull_request_target: types: [closed] branches: - - "master" + - "master-disable" jobs: cross-repo: diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml index 07f1bacaa45..0fe3e5523d3 100644 --- a/.github/workflows/validate-merge.yml +++ b/.github/workflows/validate-merge.yml @@ -2,7 +2,10 @@ name: Validate Merge on: pull_request: - branches: [master] + branches: + - master + - main + - ci jobs: validate-merge: @@ -19,6 +22,10 @@ jobs: - name: Validate run: | + git config --global url."https://${USERNAME}:${TOKEN}@git.pubmatic.com".insteadOf "https://git.pubmatic.com" ./validate.sh --nofmt --cov --race 10 env: GO111MODULE: "on" + GOPRIVATE: "git.pubmatic.com/PubMatic/*" + TOKEN: ${{ secrets.PM_OPENWRAP_CICD_PASSWORD }} + USERNAME: ${{ secrets.PM_OPENWRAP_CICD_USERNAME }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 9047e1f468f..b28ac8ee5f8 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -28,6 +28,10 @@ jobs: - name: Validate run: | + git config --global url."https://${USERNAME}:${TOKEN}@git.pubmatic.com".insteadOf "https://git.pubmatic.com" ./validate.sh --nofmt --cov --race 10 env: GO111MODULE: "on" + GOPRIVATE: "git.pubmatic.com/PubMatic/*" + TOKEN: ${{ secrets.PM_OPENWRAP_CICD_PASSWORD }} + USERNAME: ${{ secrets.PM_OPENWRAP_CICD_USERNAME }} diff --git a/.gitignore b/.gitignore index 0df7cde54fd..16991e66666 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ inventory_url.yaml # generated log files during tests analytics/config/testFiles/ +analytics/config/xyz* analytics/filesystem/testFiles/ # autogenerated version file @@ -56,3 +57,5 @@ analytics/filesystem/testFiles/ *~ *.swp *.swo +pbsimage +manual_build diff --git a/Makefile b/Makefile index 2d8aae6c78a..035b21cf41f 100644 --- a/Makefile +++ b/Makefile @@ -33,3 +33,26 @@ image: # format runs format format: ./scripts/format.sh -f true + +mockgen: mockgeninstall mockgendb mockgencache mockgenmetrics + +# export GOPATH=~/go ; GOBIN=~/go/bin; export PATH=$PATH:$GOBIN +mockgeninstall: + go install github.com/golang/mock/mockgen@v1.6.0 + +mockgendb: + mkdir -p modules/pubmatic/openwrap/database/mock modules/pubmatic/openwrap/database/mock_driver + mockgen database/sql/driver Driver,Connector,Conn,DriverContext > modules/pubmatic/openwrap/database/mock_driver/mock.go + mockgen github.com/PubMatic-OpenWrap/prebid-server/v2/modules/pubmatic/openwrap/database Database > modules/pubmatic/openwrap/database/mock/mock.go + +mockgencache: + mkdir -p modules/pubmatic/openwrap/cache/mock + mockgen github.com/PubMatic-OpenWrap/prebid-server/v2/modules/pubmatic/openwrap/cache Cache > modules/pubmatic/openwrap/cache/mock/mock.go + +mockgenmetrics: + mkdir -p modules/pubmatic/openwrap/metrics/mock + mockgen github.com/PubMatic-OpenWrap/prebid-server/v2/modules/pubmatic/openwrap/metrics MetricsEngine > modules/pubmatic/openwrap/metrics/mock/mock.go + +mockgenlogger: + mkdir -p analytics/pubmatic/mhttp/mock + mockgen github.com/PubMatic-OpenWrap/prebid-server/v2/analytics/pubmatic/mhttp HttpCallInterface,MultiHttpContextInterface > analytics/pubmatic/mhttp/mock/mock.go diff --git a/README.md b/README.md index cb64ed9a3b1..b041a3a10c9 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ Download and prepare Prebid Server: ```bash cd YOUR_DIRECTORY -git clone https://github.com/prebid/prebid-server src/github.com/prebid/prebid-server -cd src/github.com/prebid/prebid-server +git clone https://github.com/PubMatic-OpenWrap/prebid-server src/github.com/PubMatic-OpenWrap/prebid-server +cd src/github.com/PubMatic-OpenWrap/prebid-server ``` Run the automated tests: @@ -69,11 +69,10 @@ of exported types. Want to [add an adapter](https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html)? Found a bug? Great! -Report bugs, request features, and suggest improvements [on Github](https://github.com/prebid/prebid-server/issues). - -Or better yet, [open a pull request](https://github.com/prebid/prebid-server/compare) with the changes you'd like to see. +Or better yet, [open a pull request](https://github.com/PubMatic-OpenWrap/prebid-server/compare) with the changes you'd like to see. ## IDE Recommendations The quickest way to start developing Prebid Server in a reproducible environment isolated from your host OS is by using Visual Studio Code with [Remote Container Setup](devcontainer.md). + diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index ad3be2dbb2d..b0e09ff1e12 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -140,16 +140,17 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E return nil, append(errs, err) } + // Commenting out the following piece of code to avoid populating adpod_id in the Appnexus request (ref: https://inside.pubmatic.com:9443/jira/browse/UOE-6196) // For long form requests if adpodId feature enabled, adpod_id must be sent downstream. // Adpod id is a unique identifier for pod // All impressions in the same pod must have the same pod id in request extension // For this all impressions in request should belong to the same pod // If impressions number per pod is more than maxImpsPerReq - divide those imps to several requests but keep pod id the same // If adpodId feature disabled and impressions number per pod is more than maxImpsPerReq - divide those imps to several requests but do not include ad pod id - if isVIDEO == 1 && *shouldGenerateAdPodId { + /*if isVIDEO == 1 && *shouldGenerateAdPodId { requests, errors := a.buildAdPodRequests(request.Imp, request, reqExt, reqExtAppnexus, requestURI.String()) return requests, append(errs, errors...) - } + }*/ requests, errors := splitRequests(request.Imp, request, reqExt, reqExtAppnexus, requestURI.String()) return requests, append(errs, errors...) diff --git a/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json b/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json index 8f0f15262c4..df9472a85c9 100644 --- a/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json +++ b/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json @@ -47,8 +47,7 @@ "id": "test-request-id", "ext": { "appnexus": { - "adpod_id": "10", - "hb_source": 6 + "hb_source": 6 } }, "imp": [ @@ -94,4 +93,4 @@ } ], "expectedBidResponses": [{"currency":"USD","bids":[]}] - } + } \ No newline at end of file diff --git a/adapters/bidder.go b/adapters/bidder.go index b56f02e5156..e7716b40fd2 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -100,6 +100,7 @@ type TypedBid struct { BidMeta *openrtb_ext.ExtBidPrebidMeta BidType openrtb_ext.BidType BidVideo *openrtb_ext.ExtBidPrebidVideo + BidTargets map[string]string DealPriority int Seat openrtb_ext.BidderName } @@ -115,12 +116,20 @@ type ResponseData struct { Headers http.Header } +type BidRequestParams struct { + ImpIndex int + VASTTagIndex int +} + // RequestData packages together the fields needed to make an http.Request. type RequestData struct { + Params *BidRequestParams Method string Uri string Body []byte Headers http.Header + + BidderName openrtb_ext.BidderName `json:"-"` } // ExtImpBidder can be used by Bidders to unmarshal any request.imp[i].ext. diff --git a/adapters/conversant/conversant.go b/adapters/conversant/conversant.go index 56ed4b0865c..871592b6a14 100644 --- a/adapters/conversant/conversant.go +++ b/adapters/conversant/conversant.go @@ -73,7 +73,7 @@ func (c *ConversantAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo * } func parseCnvrParams(imp *openrtb2.Imp, cnvrExt openrtb_ext.ExtImpConversant) { - imp.DisplayManager = "prebid-s2s" + imp.DisplayManager = "pubmatic-openwrap" imp.DisplayManagerVer = "2.0.0" if imp.BidFloor <= 0 && cnvrExt.BidFloor > 0 { diff --git a/adapters/conversant/conversanttest/exemplary/banner.json b/adapters/conversant/conversanttest/exemplary/banner.json index 472e18f712d..9c3569eb40d 100644 --- a/adapters/conversant/conversanttest/exemplary/banner.json +++ b/adapters/conversant/conversanttest/exemplary/banner.json @@ -41,7 +41,7 @@ "tagid": "mytag", "secure": 1, "bidfloor": 0.01, - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "banner": { "format": [{"w": 300, "h": 250}] diff --git a/adapters/conversant/conversanttest/exemplary/simple_app.json b/adapters/conversant/conversanttest/exemplary/simple_app.json index 303c60f75a9..d094f1f2c7f 100644 --- a/adapters/conversant/conversanttest/exemplary/simple_app.json +++ b/adapters/conversant/conversanttest/exemplary/simple_app.json @@ -45,7 +45,7 @@ "tagid": "mytag", "secure": 1, "bidfloor": 0.01, - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "banner": { "format": [{"w": 300, "h": 250}] diff --git a/adapters/conversant/conversanttest/exemplary/video.json b/adapters/conversant/conversanttest/exemplary/video.json index 475dd796262..56606aab461 100644 --- a/adapters/conversant/conversanttest/exemplary/video.json +++ b/adapters/conversant/conversanttest/exemplary/video.json @@ -49,7 +49,7 @@ "tagid": "mytag", "secure": 1, "bidfloor": 0.01, - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "video": { "w": 300, diff --git a/adapters/conversant/conversanttest/supplemental/server_badresponse.json b/adapters/conversant/conversanttest/supplemental/server_badresponse.json index 96cb4b46452..0cab9523ba6 100644 --- a/adapters/conversant/conversanttest/supplemental/server_badresponse.json +++ b/adapters/conversant/conversanttest/supplemental/server_badresponse.json @@ -30,7 +30,7 @@ "imp": [ { "id": "1", - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "banner": { "format": [{"w": 300, "h": 250}] diff --git a/adapters/conversant/conversanttest/supplemental/server_nocontent.json b/adapters/conversant/conversanttest/supplemental/server_nocontent.json index ad86d19d6b2..15707c757e3 100644 --- a/adapters/conversant/conversanttest/supplemental/server_nocontent.json +++ b/adapters/conversant/conversanttest/supplemental/server_nocontent.json @@ -30,7 +30,7 @@ "imp": [ { "id": "1", - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "banner": { "format": [{"w": 300, "h": 250}] diff --git a/adapters/conversant/conversanttest/supplemental/server_unknownstatus.json b/adapters/conversant/conversanttest/supplemental/server_unknownstatus.json index 85586f066c6..cabfeaf321c 100644 --- a/adapters/conversant/conversanttest/supplemental/server_unknownstatus.json +++ b/adapters/conversant/conversanttest/supplemental/server_unknownstatus.json @@ -30,7 +30,7 @@ "imp": [ { "id": "1", - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "banner": { "format": [{"w": 300, "h": 250}] diff --git a/adapters/conversant/conversanttest/supplemental/test_params.json b/adapters/conversant/conversanttest/supplemental/test_params.json index 403bcc42226..cf71299df0f 100644 --- a/adapters/conversant/conversanttest/supplemental/test_params.json +++ b/adapters/conversant/conversanttest/supplemental/test_params.json @@ -107,7 +107,7 @@ "bidfloor": 7, "secure": 1, "tagid": "mytag", - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "video": { "api": [1,2], @@ -126,7 +126,7 @@ "bidfloor": 1, "secure": 1, "tagid": "mytag", - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "video": { "api": [1,2], @@ -154,7 +154,7 @@ "bidfloor": 7, "secure": 1, "tagid": "mytag", - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "video": { "api": [1,2], @@ -182,7 +182,7 @@ "bidfloor": -3, "secure": 1, "tagid": "mytag", - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "video": { "api": [1,2], diff --git a/adapters/infoawarebidder_ow_test.go b/adapters/infoawarebidder_ow_test.go new file mode 100644 index 00000000000..7089e919033 --- /dev/null +++ b/adapters/infoawarebidder_ow_test.go @@ -0,0 +1,194 @@ +package adapters + +import ( + "errors" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestAppNotSupportedOW(t *testing.T) { + bidder := &mockBidder{} + info := config.BidderInfo{ + Capabilities: &config.CapabilitiesInfo{ + Site: &config.PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, + }, + } + constrained := BuildInfoAwareBidder(bidder, info) + bids, errs := constrained.MakeRequests(&openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ID: "imp-1", Banner: &openrtb2.Banner{}}}, + App: &openrtb2.App{}, + }, &ExtraRequestInfo{}) + if !assert.Len(t, errs, 1) { + return + } + assert.EqualError(t, errs[0], "this bidder does not support app requests") + assert.IsType(t, &errortypes.Warning{}, errs[0]) + assert.Len(t, bids, 0) +} + +func TestSiteNotSupported(t *testing.T) { + bidder := &mockBidder{} + info := config.BidderInfo{ + Capabilities: &config.CapabilitiesInfo{ + App: &config.PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, + }, + } + constrained := BuildInfoAwareBidder(bidder, info) + bids, errs := constrained.MakeRequests(&openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ID: "imp-1", Banner: &openrtb2.Banner{}}}, + Site: &openrtb2.Site{}, + }, &ExtraRequestInfo{}) + if !assert.Len(t, errs, 1) { + return + } + assert.EqualError(t, errs[0], "this bidder does not support site requests") + assert.IsType(t, &errortypes.Warning{}, errs[0]) + assert.Len(t, bids, 0) +} + +func TestImpFiltering(t *testing.T) { + bidder := &mockBidder{} + info := config.BidderInfo{ + Capabilities: &config.CapabilitiesInfo{ + Site: &config.PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}, + }, + App: &config.PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, + }, + } + + constrained := BuildInfoAwareBidder(bidder, info) + + testCases := []struct { + description string + inBidRequest *openrtb2.BidRequest + expectedErrors []error + expectedImpLen int + }{ + { + description: "Empty Imp array. MakeRequest() call not expected", + inBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{}, + Site: &openrtb2.Site{}, + }, + expectedErrors: []error{ + &errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"}, + }, + expectedImpLen: 0, + }, + { + description: "Sole imp in bid request is of wrong media type. MakeRequest() call not expected", + inBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ID: "imp-1", Video: &openrtb2.Video{}}}, + App: &openrtb2.App{}, + }, + expectedErrors: []error{ + &errortypes.BadInput{Message: "request.imp[0] uses video, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"}, + }, + expectedImpLen: 0, + }, + { + description: "All imps in bid request of wrong media type, MakeRequest() call not expected", + inBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-1", Video: &openrtb2.Video{}}, + {ID: "imp-2", Native: &openrtb2.Native{}}, + {ID: "imp-3", Audio: &openrtb2.Audio{}}, + }, + App: &openrtb2.App{}, + }, + expectedErrors: []error{ + &errortypes.BadInput{Message: "request.imp[0] uses video, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "request.imp[1] uses native, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "request.imp[2] uses audio, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"}, + }, + expectedImpLen: 0, + }, + { + description: "Some imps with correct media type, MakeRequest() call expected", + inBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp-1", + Video: &openrtb2.Video{}, + }, + { + Native: &openrtb2.Native{}, + }, + { + ID: "imp-2", + Video: &openrtb2.Video{}, + Native: &openrtb2.Native{}, + }, + { + Banner: &openrtb2.Banner{}, + }, + }, + Site: &openrtb2.Site{}, + }, + expectedErrors: []error{ + &errortypes.BadInput{Message: "request.imp[1] uses native, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "request.imp[2] uses native, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "request.imp[3] uses banner, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "request.imp[1] has no supported MediaTypes. It will be ignored"}, + &errortypes.BadInput{Message: "request.imp[3] has no supported MediaTypes. It will be ignored"}, + }, + expectedImpLen: 2, + }, + { + description: "All imps with correct media type, MakeRequest() call expected", + inBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-1", Video: &openrtb2.Video{}}, + {ID: "imp-2", Video: &openrtb2.Video{}}, + }, + Site: &openrtb2.Site{}, + }, + expectedErrors: nil, + expectedImpLen: 2, + }, + } + + for _, test := range testCases { + actualAdapterRequests, actualErrs := constrained.MakeRequests(test.inBidRequest, &ExtraRequestInfo{}) + + // Assert the request.Imp slice was correctly filtered and if MakeRequest() was called by asserting + // the corresponding error messages were returned + for i, expectedErr := range test.expectedErrors { + assert.EqualError(t, expectedErr, actualErrs[i].Error(), "Test failed. Error[%d] in error list mismatch: %s", i, test.description) + } + + // Extra MakeRequests() call check: our mockBidder returns an adapter request for every imp + assert.Len(t, actualAdapterRequests, test.expectedImpLen, "Test failed. Incorrect length of filtered imps: %s", test.description) + } +} + +type mockBidder struct { +} + +func (m *mockBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *ExtraRequestInfo) ([]*RequestData, []error) { + var adapterRequests []*RequestData + + for i := 0; i < len(request.Imp); i++ { + adapterRequests = append(adapterRequests, &RequestData{}) + } + + return adapterRequests, nil +} + +func (m *mockBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *RequestData, response *ResponseData) (*BidderResponse, []error) { + return nil, []error{errors.New("mock MakeBids error")} +} diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index 7ab0248b937..a03dfa4c82d 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -6,6 +6,7 @@ import ( "fmt" "math" "net/http" + "net/url" "strconv" "strings" @@ -19,7 +20,19 @@ import ( const MAX_IMPRESSIONS_PUBMATIC = 30 -const ae = "ae" +const ( + ae = "ae" + PUBMATIC = "[PUBMATIC]" + buyId = "buyid" + buyIdTargetingKey = "hb_buyid_" + skAdnetworkKey = "skadn" + rewardKey = "reward" + dctrKeywordName = "dctr" + urlEncodedEqualChar = "%3D" + AdServerKey = "adserver" + PBAdslotKey = "pbadslot" + bidViewability = "bidViewability" +) type PubmaticAdapter struct { URI string @@ -31,11 +44,15 @@ type pubmaticBidExt struct { VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` Marketplace string `json:"marketplace,omitempty"` PrebidDealPriority int `json:"prebiddealpriority,omitempty"` + DspId int `json:"dspid,omitempty"` + AdvertiserID int `json:"advid,omitempty"` } type pubmaticWrapperExt struct { ProfileID int `json:"profile,omitempty"` VersionID int `json:"version,omitempty"` + + WrapperImpID string `json:"wiid,omitempty"` } type pubmaticBidExtVideo struct { @@ -44,8 +61,9 @@ type pubmaticBidExtVideo struct { type ExtImpBidderPubmatic struct { adapters.ExtImpBidder - Data json.RawMessage `json:"data,omitempty"` - AE int `json:"ae,omitempty"` + Data json.RawMessage `json:"data,omitempty"` + AE int `json:"ae,omitempty"` + SKAdnetwork json.RawMessage `json:"skadn,omitempty"` } type ExtAdServer struct { @@ -69,13 +87,11 @@ type respExt struct { } const ( - dctrKeyName = "key_val" - pmZoneIDKeyName = "pmZoneId" - pmZoneIDKeyNameOld = "pmZoneID" - ImpExtAdUnitKey = "dfp_ad_unit_code" - AdServerGAM = "gam" - AdServerKey = "adserver" - PBAdslotKey = "pbadslot" + dctrKeyName = "key_val" + pmZoneIDKeyName = "pmZoneId" + pmZoneIDRequestParamName = "pmzoneid" + ImpExtAdUnitKey = "dfp_ad_unit_code" + AdServerGAM = "gam" ) func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { @@ -85,7 +101,7 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad extractWrapperExtFromImp := true extractPubIDFromImp := true - newReqExt, err := extractPubmaticExtFromRequest(request) + newReqExt, cookies, err := extractPubmaticExtFromRequest(request) if err != nil { return nil, []error{err} } @@ -117,6 +133,10 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad wrapperExt.VersionID = wrapperExtFromImp.VersionID } + if wrapperExt.WrapperImpID == "" { + wrapperExt.WrapperImpID = wrapperExtFromImp.WrapperImpID + } + if wrapperExt != nil && wrapperExt.ProfileID != 0 && wrapperExt.VersionID != 0 { extractWrapperExtFromImp = false } @@ -163,6 +183,48 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad request.App = &appCopy } + // move user.ext.eids to user.eids + if request.User != nil && request.User.Ext != nil { + var userExt *openrtb_ext.ExtUser + if err = json.Unmarshal(request.User.Ext, &userExt); err == nil { + if userExt != nil && userExt.Eids != nil { + var eidArr []openrtb2.EID + for _, eid := range userExt.Eids { + newEid := &openrtb2.EID{ + ID: eid.ID, + Source: eid.Source, + Ext: eid.Ext, + } + var uidArr []openrtb2.UID + for _, uid := range eid.UIDs { + newUID := &openrtb2.UID{ + ID: uid.ID, + AType: uid.AType, + Ext: uid.Ext, + } + uidArr = append(uidArr, *newUID) + } + newEid.UIDs = uidArr + eidArr = append(eidArr, *newEid) + } + + user := *request.User + user.EIDs = eidArr + userExt.Eids = nil + updatedUserExt, err1 := json.Marshal(userExt) + if err1 == nil { + user.Ext = updatedUserExt + } + request.User = &user + } + } + } + + //adding hack to support DNT, since hbopenbid does not support lmt + if request.Device != nil && request.Device.Lmt != nil && *request.Device.Lmt != 0 { + request.Device.DNT = request.Device.Lmt + } + reqJSON, err := json.Marshal(request) if err != nil { errs = append(errs, err) @@ -172,6 +234,9 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") + for _, line := range cookies { + headers.Add("Cookie", line) + } return []*adapters.RequestData{{ Method: "POST", Uri: a.URI, @@ -200,26 +265,26 @@ func validateAdSlot(adslot string, imp *openrtb2.Imp) error { adSize := strings.Split(strings.ToLower(adSlot[1]), "x") if len(adSize) != 2 { - return fmt.Errorf("Invalid size provided in adSlot %v", adSlotStr) + return errors.New(fmt.Sprintf("Invalid size provided in adSlot %v", adSlotStr)) } width, err := strconv.Atoi(strings.TrimSpace(adSize[0])) if err != nil { - return fmt.Errorf("Invalid width provided in adSlot %v", adSlotStr) + return errors.New(fmt.Sprintf("Invalid width provided in adSlot %v", adSlotStr)) } heightStr := strings.Split(adSize[1], ":") height, err := strconv.Atoi(strings.TrimSpace(heightStr[0])) if err != nil { - return fmt.Errorf("Invalid height provided in adSlot %v", adSlotStr) + return errors.New(fmt.Sprintf("Invalid height provided in adSlot %v", adSlotStr)) } //In case of video, size could be derived from the player size - if imp.Banner != nil { + if imp.Banner != nil && width != 0 && height != 0 && !(imp.Native != nil && width == 1 && height == 1) { imp.Banner = assignBannerWidthAndHeight(imp.Banner, int64(width), int64(height)) } } else { - return fmt.Errorf("Invalid adSlot %v", adSlotStr) + return errors.New(fmt.Sprintf("Invalid adSlot %v", adSlotStr)) } return nil @@ -230,6 +295,10 @@ func assignBannerSize(banner *openrtb2.Banner) (*openrtb2.Banner, error) { return banner, nil } + if len(banner.Format) == 0 { + return nil, errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format)) + } + return assignBannerWidthAndHeight(banner, banner.Format[0].W, banner.Format[0].H), nil } @@ -308,9 +377,25 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP extMap[pmZoneIDKeyName] = pubmaticExt.PmZoneID } + if bidderExt.SKAdnetwork != nil { + extMap[skAdnetworkKey] = bidderExt.SKAdnetwork + } + + if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory != nil && *bidderExt.Prebid.IsRewardedInventory == 1 { + extMap[rewardKey] = *bidderExt.Prebid.IsRewardedInventory + } + if len(bidderExt.Data) > 0 { populateFirstPartyDataImpAttributes(bidderExt.Data, extMap) } + // If bidViewabilityScore param is populated, pass it to imp[i].ext + if pubmaticExt.BidViewabilityScore != nil { + extMap[bidViewability] = pubmaticExt.BidViewabilityScore + } + + if bidderExt.AE != 0 { + extMap[ae] = bidderExt.AE + } if bidderExt.AE != 0 { extMap[ae] = bidderExt.AE @@ -327,19 +412,25 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP return wrapExt, pubID, nil } +// roundToFourDecimals retuns given value to 4 decimal points +func roundToFourDecimals(in float64) float64 { + return math.Round(in*10000) / 10000 +} + // extractPubmaticExtFromRequest parse the req.ext to fetch wrapper and acat params -func extractPubmaticExtFromRequest(request *openrtb2.BidRequest) (extRequestAdServer, error) { +func extractPubmaticExtFromRequest(request *openrtb2.BidRequest) (extRequestAdServer, []string, error) { + var cookies []string // req.ext.prebid would always be there and Less nil cases to handle, more safe! var pmReqExt extRequestAdServer if request == nil || len(request.Ext) == 0 { - return pmReqExt, nil + return pmReqExt, cookies, nil } reqExt := &openrtb_ext.ExtRequest{} err := json.Unmarshal(request.Ext, &reqExt) if err != nil { - return pmReqExt, fmt.Errorf("error decoding Request.ext : %s", err.Error()) + return pmReqExt, cookies, fmt.Errorf("error decoding Request.ext : %s", err.Error()) } pmReqExt.ExtRequest = *reqExt @@ -347,7 +438,7 @@ func extractPubmaticExtFromRequest(request *openrtb2.BidRequest) (extRequestAdSe if reqExt.Prebid.BidderParams != nil { err = json.Unmarshal(reqExt.Prebid.BidderParams, &reqExtBidderParams) if err != nil { - return pmReqExt, err + return pmReqExt, cookies, err } } @@ -356,7 +447,7 @@ func extractPubmaticExtFromRequest(request *openrtb2.BidRequest) (extRequestAdSe wrpExt := &pubmaticWrapperExt{} err = json.Unmarshal(wrapperObj, wrpExt) if err != nil { - return pmReqExt, err + return pmReqExt, cookies, err } pmReqExt.Wrapper = wrpExt } @@ -365,7 +456,7 @@ func extractPubmaticExtFromRequest(request *openrtb2.BidRequest) (extRequestAdSe var acat []string err = json.Unmarshal(acatBytes, &acat) if err != nil { - return pmReqExt, err + return pmReqExt, cookies, err } for i := 0; i < len(acat); i++ { acat[i] = strings.TrimSpace(acat[i]) @@ -377,7 +468,19 @@ func extractPubmaticExtFromRequest(request *openrtb2.BidRequest) (extRequestAdSe pmReqExt.Marketplace = &marketplaceReqExt{AllowedBidders: allowedBidders} } - return pmReqExt, nil + // OW patch -start- + if wiid, ok := reqExtBidderParams["wiid"]; ok { + if pmReqExt.Wrapper == nil { + pmReqExt.Wrapper = &pubmaticWrapperExt{} + } + pmReqExt.Wrapper.WrapperImpID, _ = strconv.Unquote(string(wiid)) + } + if wrapperObj, present := reqExtBidderParams["Cookie"]; present && len(wrapperObj) != 0 { + err = json.Unmarshal(wrapperObj, &cookies) + } + // OW patch -end- + + return pmReqExt, cookies, nil } func getAlternateBidderCodesFromRequestExt(reqExt *openrtb_ext.ExtRequest) []string { @@ -404,10 +507,20 @@ func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[s continue } else { key := keyVal.Key - if keyVal.Key == pmZoneIDKeyNameOld { + val := strings.Join(keyVal.Values[:], ",") + if strings.EqualFold(key, pmZoneIDRequestParamName) { key = pmZoneIDKeyName + } else if key == dctrKeywordName { + key = dctrKeyName + // URL-decode dctr value if it is url-encoded + if strings.Contains(val, urlEncodedEqualChar) { + urlDecodedVal, err := url.QueryUnescape(val) + if err == nil { + val = urlDecodedVal + } + } } - extMap[key] = strings.Join(keyVal.Values[:], ",") + extMap[key] = val } } } @@ -436,16 +549,18 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa var errs []error for _, sb := range bidResp.SeatBid { + targets := getTargetingKeys(sb.Ext, string(externalRequest.BidderName)) for i := 0; i < len(sb.Bid); i++ { bid := sb.Bid[i] - if len(bid.Cat) > 1 { - bid.Cat = bid.Cat[0:1] - } + + // Copy SeatBid Ext to Bid.Ext + bid.Ext = copySBExtToBidExt(sb.Ext, bid.Ext) typedBid := &adapters.TypedBid{ - Bid: &bid, - BidType: openrtb_ext.BidTypeBanner, - BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, + Bid: &bid, + BidType: openrtb_ext.BidTypeBanner, + BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, + BidTargets: targets, } var bidExt *pubmaticBidExt @@ -462,6 +577,11 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa if bidExt.VideoCreativeInfo != nil && bidExt.VideoCreativeInfo.Duration != nil { typedBid.BidVideo.Duration = *bidExt.VideoCreativeInfo.Duration } + //prepares ExtBidPrebidMeta with Values got from bidresponse + typedBid.BidMeta = prepareMetaObject(bid, bidExt, sb.Seat) + } + if len(bid.Cat) > 1 { + bid.Cat = bid.Cat[0:1] } if typedBid.BidType == openrtb_ext.BidTypeNative { diff --git a/adapters/pubmatic/pubmatic_ow.go b/adapters/pubmatic/pubmatic_ow.go new file mode 100644 index 00000000000..a3d43a90882 --- /dev/null +++ b/adapters/pubmatic/pubmatic_ow.go @@ -0,0 +1,76 @@ +package pubmatic + +import ( + "encoding/json" + "strconv" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func getTargetingKeys(bidExt json.RawMessage, bidderName string) map[string]string { + targets := map[string]string{} + if bidExt != nil { + bidExtMap := make(map[string]interface{}) + err := json.Unmarshal(bidExt, &bidExtMap) + if err == nil && bidExtMap[buyId] != nil { + targets[buyIdTargetingKey+bidderName], _ = bidExtMap[buyId].(string) + } + } + return targets +} + +func copySBExtToBidExt(sbExt json.RawMessage, bidExt json.RawMessage) json.RawMessage { + if sbExt != nil { + sbExtMap := getMapFromJSON(sbExt) + bidExtMap := make(map[string]interface{}) + if bidExt != nil { + bidExtMap = getMapFromJSON(bidExt) + } + if bidExtMap != nil && sbExtMap != nil { + if sbExtMap[buyId] != nil && bidExtMap[buyId] == nil { + bidExtMap[buyId] = sbExtMap[buyId] + } + } + byteAra, _ := json.Marshal(bidExtMap) + return json.RawMessage(byteAra) + } + return bidExt +} + +// prepareMetaObject prepares the Meta structure using Bid Response +func prepareMetaObject(bid openrtb2.Bid, bidExt *pubmaticBidExt, seat string) *openrtb_ext.ExtBidPrebidMeta { + + meta := &openrtb_ext.ExtBidPrebidMeta{ + NetworkID: bidExt.DspId, + AdvertiserID: bidExt.AdvertiserID, + MediaType: string(getBidType(bidExt)), + } + + if meta.NetworkID != 0 { + meta.DemandSource = strconv.Itoa(meta.NetworkID) + } + + if len(seat) > 0 { + meta.AdvertiserID, _ = strconv.Atoi(seat) + } + + meta.AgencyID = meta.AdvertiserID + + if len(bid.Cat) > 0 { + meta.PrimaryCategoryID = bid.Cat[0] + meta.SecondaryCategoryIDs = bid.Cat + } + + // NOTE: We will not recieve below fields from the translator response also not sure on what will be the key names for these in the response, + // when we needed we can add it back. + // New fields added, assignee fields name may change + // Assign meta.BrandId to bidExt.ADomain[0] //BrandID is of Type int and ADomain values if string type like "mystartab.com" + // meta.NetworkName = bidExt.NetworkName; + // meta.AdvertiserName = bidExt.AdvertiserName; + // meta.AgencyName = bidExt.AgencyName; + // meta.BrandName = bidExt.BrandName; + // meta.DChain = bidExt.DChain; + + return meta +} diff --git a/adapters/pubmatic/pubmatic_ow_test.go b/adapters/pubmatic/pubmatic_ow_test.go new file mode 100644 index 00000000000..f3cfa1d9729 --- /dev/null +++ b/adapters/pubmatic/pubmatic_ow_test.go @@ -0,0 +1,249 @@ +package pubmatic + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestGetAdServerTargetingForEmptyExt(t *testing.T) { + ext := json.RawMessage(`{}`) + targets := getTargetingKeys(ext, "pubmatic") + // banner is the default bid type when no bidType key is present in the bid.ext + if targets != nil && targets["hb_buyid_pubmatic"] != "" { + t.Errorf("It should not contained AdserverTageting") + } +} + +func TestGetAdServerTargetingForValidExt(t *testing.T) { + ext := json.RawMessage("{\"buyid\":\"testBuyId\"}") + targets := getTargetingKeys(ext, "pubmatic") + // banner is the default bid type when no bidType key is present in the bid.ext + if targets == nil { + t.Error("It should have targets") + t.FailNow() + } + if targets != nil && targets["hb_buyid_pubmatic"] != "testBuyId" { + t.Error("It should have testBuyId as targeting") + t.FailNow() + } +} + +func TestGetAdServerTargetingForPubmaticAlias(t *testing.T) { + ext := json.RawMessage("{\"buyid\":\"testBuyId-alias\"}") + targets := getTargetingKeys(ext, "dummy-alias") + // banner is the default bid type when no bidType key is present in the bid.ext + if targets == nil { + t.Error("It should have targets") + t.FailNow() + } + if targets != nil && targets["hb_buyid_dummy-alias"] != "testBuyId-alias" { + t.Error("It should have testBuyId as targeting") + t.FailNow() + } +} + +func TestCopySBExtToBidExtWithBidExt(t *testing.T) { + sbext := json.RawMessage("{\"buyid\":\"testBuyId\"}") + bidext := json.RawMessage("{\"dspId\":\"9\"}") + // expectedbid := json.RawMessage("{\"dspId\":\"9\",\"buyid\":\"testBuyId\"}") + bidextnew := copySBExtToBidExt(sbext, bidext) + if bidextnew == nil { + t.Errorf("it should not be nil") + } +} + +func TestCopySBExtToBidExtWithNoBidExt(t *testing.T) { + sbext := json.RawMessage("{\"buyid\":\"testBuyId\"}") + bidext := json.RawMessage("{\"dspId\":\"9\"}") + // expectedbid := json.RawMessage("{\"dspId\":\"9\",\"buyid\":\"testBuyId\"}") + bidextnew := copySBExtToBidExt(sbext, bidext) + if bidextnew == nil { + t.Errorf("it should not be nil") + } +} + +func TestCopySBExtToBidExtWithNoSeatExt(t *testing.T) { + bidext := json.RawMessage("{\"dspId\":\"9\"}") + // expectedbid := json.RawMessage("{\"dspId\":\"9\",\"buyid\":\"testBuyId\"}") + bidextnew := copySBExtToBidExt(nil, bidext) + if bidextnew == nil { + t.Errorf("it should not be nil") + } +} + +func TestPrepareMetaObject(t *testing.T) { + typebanner := 0 + typevideo := 1 + typenative := 2 + typeinvalid := 233 + type args struct { + bid openrtb2.Bid + bidExt *pubmaticBidExt + seat string + } + tests := []struct { + name string + args args + want *openrtb_ext.ExtBidPrebidMeta + }{ + { + name: "Empty Meta Object and default BidType banner", + args: args{ + bid: openrtb2.Bid{ + Cat: []string{}, + }, + bidExt: &pubmaticBidExt{}, + seat: "", + }, + want: &openrtb_ext.ExtBidPrebidMeta{ + MediaType: "banner", + }, + }, + { + name: "Valid Meta Object with Empty Seatbid.seat", + args: args{ + bid: openrtb2.Bid{ + Cat: []string{"IAB-1", "IAB-2"}, + }, + bidExt: &pubmaticBidExt{ + DspId: 80, + AdvertiserID: 139, + BidType: &typeinvalid, + }, + seat: "", + }, + want: &openrtb_ext.ExtBidPrebidMeta{ + NetworkID: 80, + DemandSource: "80", + PrimaryCategoryID: "IAB-1", + SecondaryCategoryIDs: []string{"IAB-1", "IAB-2"}, + AdvertiserID: 139, + AgencyID: 139, + MediaType: "banner", + }, + }, + { + name: "Valid Meta Object with Empty bidExt.DspId", + args: args{ + bid: openrtb2.Bid{ + Cat: []string{"IAB-1", "IAB-2"}, + }, + bidExt: &pubmaticBidExt{ + DspId: 0, + AdvertiserID: 139, + }, + seat: "124", + }, + want: &openrtb_ext.ExtBidPrebidMeta{ + NetworkID: 0, + DemandSource: "", + PrimaryCategoryID: "IAB-1", + SecondaryCategoryIDs: []string{"IAB-1", "IAB-2"}, + AdvertiserID: 124, + AgencyID: 124, + MediaType: "banner", + }, + }, + { + name: "Valid Meta Object with Empty Seatbid.seat and Empty bidExt.AdvertiserID", + args: args{ + bid: openrtb2.Bid{ + Cat: []string{"IAB-1", "IAB-2"}, + }, + bidExt: &pubmaticBidExt{ + DspId: 80, + AdvertiserID: 0, + }, + seat: "", + }, + want: &openrtb_ext.ExtBidPrebidMeta{ + NetworkID: 80, + DemandSource: "80", + PrimaryCategoryID: "IAB-1", + SecondaryCategoryIDs: []string{"IAB-1", "IAB-2"}, + AdvertiserID: 0, + AgencyID: 0, + MediaType: "banner", + }, + }, + { + name: "Valid Meta Object with Empty CategoryIds and BidType video", + args: args{ + bid: openrtb2.Bid{ + Cat: []string{}, + }, + bidExt: &pubmaticBidExt{ + DspId: 80, + AdvertiserID: 139, + BidType: &typevideo, + }, + seat: "124", + }, + want: &openrtb_ext.ExtBidPrebidMeta{ + NetworkID: 80, + DemandSource: "80", + PrimaryCategoryID: "", + AdvertiserID: 124, + AgencyID: 124, + MediaType: "video", + }, + }, + { + name: "Valid Meta Object with Single CategoryId and BidType native", + args: args{ + bid: openrtb2.Bid{ + Cat: []string{"IAB-1"}, + }, + bidExt: &pubmaticBidExt{ + DspId: 80, + AdvertiserID: 139, + BidType: &typenative, + }, + seat: "124", + }, + want: &openrtb_ext.ExtBidPrebidMeta{ + NetworkID: 80, + DemandSource: "80", + PrimaryCategoryID: "IAB-1", + SecondaryCategoryIDs: []string{"IAB-1"}, + AdvertiserID: 124, + AgencyID: 124, + MediaType: "native", + }, + }, + { + name: "Valid Meta Object and BidType banner", + args: args{ + bid: openrtb2.Bid{ + Cat: []string{"IAB-1", "IAB-2"}, + }, + bidExt: &pubmaticBidExt{ + DspId: 80, + AdvertiserID: 139, + BidType: &typebanner, + }, + seat: "124", + }, + want: &openrtb_ext.ExtBidPrebidMeta{ + NetworkID: 80, + DemandSource: "80", + PrimaryCategoryID: "IAB-1", + SecondaryCategoryIDs: []string{"IAB-1", "IAB-2"}, + AdvertiserID: 124, + AgencyID: 124, + MediaType: "banner", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := prepareMetaObject(tt.args.bid, tt.args.bidExt, tt.args.seat); !reflect.DeepEqual(got, tt.want) { + t.Errorf("prepareMetaObject() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index b40bf2d4fb2..3531ec6b2fa 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -88,6 +88,7 @@ func TestParseImpressionObject(t *testing.T) { expectedPublisherId string wantErr bool expectedBidfloor float64 + expectedImpExt json.RawMessage }{ { name: "imp.bidfloor empty and kadfloor set", @@ -98,6 +99,7 @@ func TestParseImpressionObject(t *testing.T) { }, }, expectedBidfloor: 0.12, + expectedImpExt: json.RawMessage(nil), }, { name: "imp.bidfloor set and kadfloor empty", @@ -109,6 +111,7 @@ func TestParseImpressionObject(t *testing.T) { }, }, expectedBidfloor: 0.12, + expectedImpExt: json.RawMessage(nil), }, { name: "imp.bidfloor set and kadfloor invalid", @@ -120,6 +123,7 @@ func TestParseImpressionObject(t *testing.T) { }, }, expectedBidfloor: 0.12, + expectedImpExt: json.RawMessage(nil), }, { name: "imp.bidfloor set and kadfloor set, higher imp.bidfloor", @@ -142,6 +146,7 @@ func TestParseImpressionObject(t *testing.T) { }, }, expectedBidfloor: 0.13, + expectedImpExt: json.RawMessage(nil), }, { name: "kadfloor string set with whitespace", @@ -153,6 +158,17 @@ func TestParseImpressionObject(t *testing.T) { }, }, expectedBidfloor: 0.13, + expectedImpExt: json.RawMessage(nil), + }, + { + name: "bidViewability Object is set in imp.ext.prebid.pubmatic, pass to imp.ext", + args: args{ + imp: &openrtb2.Imp{ + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"bidder":{"bidViewability":{"adSizes":{"728x90":{"createdAt":1679993940011,"rendered":20,"totalViewTime":424413,"viewed":17}},"adUnit":{"createdAt":1679993940011,"rendered":25,"totalViewTime":424413,"viewed":17}}}}`), + }, + }, + expectedImpExt: json.RawMessage(`{"bidViewability":{"adSizes":{"728x90":{"createdAt":1679993940011,"rendered":20,"totalViewTime":424413,"viewed":17}},"adUnit":{"createdAt":1679993940011,"rendered":25,"totalViewTime":424413,"viewed":17}}}`), }, } for _, tt := range tests { @@ -162,6 +178,7 @@ func TestParseImpressionObject(t *testing.T) { assert.Equal(t, tt.expectedWrapperExt, receivedWrapperExt) assert.Equal(t, tt.expectedPublisherId, receivedPublisherId) assert.Equal(t, tt.expectedBidfloor, tt.args.imp.BidFloor) + assert.Equal(t, tt.expectedImpExt, tt.args.imp.Ext) }) } } @@ -174,6 +191,7 @@ func TestExtractPubmaticExtFromRequest(t *testing.T) { name string args args expectedReqExt extRequestAdServer + expectedCookie []string wantErr bool }{ { @@ -289,9 +307,10 @@ func TestExtractPubmaticExtFromRequest(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotReqExt, err := extractPubmaticExtFromRequest(tt.args.request) + gotReqExt, gotCookie, err := extractPubmaticExtFromRequest(tt.args.request) assert.Equal(t, tt.wantErr, err != nil) assert.Equal(t, tt.expectedReqExt, gotReqExt) + assert.Equal(t, tt.expectedCookie, gotCookie) }) } } @@ -354,8 +373,9 @@ func TestPubmaticAdapter_MakeBids(t *testing.T) { args: args{ response: &adapters.ResponseData{ StatusCode: http.StatusOK, - Body: []byte(`{"id": "test-request-id", "seatbid":[{"seat": "958", "bid":[{"id": "7706636740145184841", "impid": "test-imp-id", "price": 0.500000, "adid": "29681110", "adm": "some-test-ad", "adomain":["pubmatic.com"], "crid": "29681110", "h": 250, "w": 300, "dealid": "testdeal", "ext":{"dspid": 6, "deal_channel": 1, "prebiddealpriority": 1}}]}], "bidid": "5778926625248726496", "cur": "USD"}`), + Body: []byte(`{"id": "test-request-id", "seatbid":[{"seat": "958", "bid":[{"id": "7706636740145184841", "impid": "test-imp-id", "price": 0.500000, "adid": "29681110", "adm": "some-test-ad", "adomain":["pubmatic.com"], "crid": "29681110", "h": 250, "w": 300, "dealid": "testdeal", "ext":{"dspid": 6, "deal_channel": 1, "prebiddealpriority": 1}}], "ext": {"buyid": "testBuyId"}}], "bidid": "5778926625248726496", "cur": "USD"}`), }, + externalRequest: &adapters.RequestData{BidderName: openrtb_ext.BidderPubmatic}, }, wantErr: nil, wantResp: &adapters.BidderResponse{ @@ -372,11 +392,19 @@ func TestPubmaticAdapter_MakeBids(t *testing.T) { H: 250, W: 300, DealID: "testdeal", - Ext: json.RawMessage(`{"dspid": 6, "deal_channel": 1, "prebiddealpriority": 1}`), + Ext: json.RawMessage(`{"buyid":"testBuyId","deal_channel":1,"dspid":6,"prebiddealpriority":1}`), }, DealPriority: 1, BidType: openrtb_ext.BidTypeBanner, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, + BidTargets: map[string]string{"hb_buyid_pubmatic": "testBuyId"}, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + AdvertiserID: 958, + AgencyID: 958, + NetworkID: 6, + DemandSource: "6", + MediaType: "banner", + }, }, }, Currency: "USD", @@ -389,6 +417,7 @@ func TestPubmaticAdapter_MakeBids(t *testing.T) { StatusCode: http.StatusOK, Body: []byte(`{"id": "test-request-id", "seatbid":[{"seat": "958", "bid":[{"id": "7706636740145184841", "impid": "test-imp-id", "price": 0.500000, "adid": "29681110", "adm": "some-test-ad", "adomain":["pubmatic.com"], "crid": "29681110", "h": 250, "w": 300, "dealid": "testdeal", "ext":{"dspid": 6, "deal_channel": 1, "prebiddealpriority": -1}}]}], "bidid": "5778926625248726496", "cur": "USD"}`), }, + externalRequest: &adapters.RequestData{BidderName: openrtb_ext.BidderPubmatic}, }, wantErr: nil, wantResp: &adapters.BidderResponse{ @@ -407,8 +436,50 @@ func TestPubmaticAdapter_MakeBids(t *testing.T) { DealID: "testdeal", Ext: json.RawMessage(`{"dspid": 6, "deal_channel": 1, "prebiddealpriority": -1}`), }, - BidType: openrtb_ext.BidTypeBanner, - BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, + BidType: openrtb_ext.BidTypeBanner, + BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, + BidTargets: map[string]string{}, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + AdvertiserID: 958, + AgencyID: 958, + NetworkID: 6, + DemandSource: "6", + MediaType: "banner", + }, + }, + }, + Currency: "USD", + }, + }, + { + name: "BidExt Nil cases", + args: args{ + response: &adapters.ResponseData{ + StatusCode: http.StatusOK, + Body: []byte(`{"id": "test-request-id", "seatbid":[{"seat": "958", "bid":[{"id": "7706636740145184841", "impid": "test-imp-id", "price": 0.500000, "adid": "29681110", "adm": "some-test-ad", "adomain":["pubmatic.com"], "crid": "29681110", "h": 250, "w": 300, "dealid": "testdeal", "ext":null}]}], "bidid": "5778926625248726496", "cur": "USD"}`), + }, + externalRequest: &adapters.RequestData{BidderName: openrtb_ext.BidderPubmatic}, + }, + wantErr: nil, + wantResp: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "7706636740145184841", + ImpID: "test-imp-id", + Price: 0.500000, + AdID: "29681110", + AdM: "some-test-ad", + ADomain: []string{"pubmatic.com"}, + CrID: "29681110", + H: 250, + W: 300, + DealID: "testdeal", + Ext: json.RawMessage(`null`), + }, + BidType: openrtb_ext.BidTypeBanner, + BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, + BidTargets: map[string]string{}, }, }, Currency: "USD", diff --git a/adapters/pubmatic/pubmatictest/exemplary/banner.json b/adapters/pubmatic/pubmatictest/exemplary/banner.json index 74150f5aa83..1e2ad911a4c 100644 --- a/adapters/pubmatic/pubmatictest/exemplary/banner.json +++ b/adapters/pubmatic/pubmatictest/exemplary/banner.json @@ -36,7 +36,8 @@ "ext": { "prebid": { "bidderparams": { - "acat": ["drg","dlu","ssr"] + "acat": ["drg","dlu","ssr"], + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" } } }, @@ -45,7 +46,7 @@ "publisher": { "id": "1234" } - } + } }, "httpCalls": [ @@ -87,12 +88,14 @@ "ext": { "wrapper": { "profile": 5123, - "version":1 + "version":1, + "wiid" : "dwzafakjflan-tygannnvlla-mlljvj" }, "acat": ["drg","dlu","ssr"], "prebid": { "bidderparams": { - "acat": ["drg","dlu","ssr"] + "acat": ["drg","dlu","ssr"], + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" } } } @@ -158,4 +161,4 @@ ] } ] - } \ No newline at end of file + } diff --git a/adapters/pubmatic/pubmatictest/exemplary/video-rewarded.json b/adapters/pubmatic/pubmatictest/exemplary/video-rewarded.json new file mode 100644 index 00000000000..cf51e3851ff --- /dev/null +++ b/adapters/pubmatic/pubmatictest/exemplary/video-rewarded.json @@ -0,0 +1,175 @@ +{ + "mockBidRequest": { + "id": "test-video-request", + "imp": [{ + "id": "test-video-imp", + "video": { + "w":640, + "h":480, + "mimes": ["video/mp4", "video/x-flv"], + "minduration": 5, + "maxduration": 30, + "startdelay": 5, + "playbackmethod": [1, 3], + "api": [1, 2], + "protocols": [2, 3], + "battr": [13, 14], + "linearity": 1, + "placement": 2, + "minbitrate": 10, + "maxbitrate": 10 + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "adSlot": "AdTag_Div1@0x0", + "publisherId": "999", + "keywords": [{ + "key": "pmZoneID", + "value": ["Zone1", "Zone2"] + } + ], + "wrapper": { + "version": 1, + "profile": 5123 + } + } + } + }], + "device":{ + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + "body": { + "id": "test-video-request", + "imp": [ + { + "id": "test-video-imp", + "tagid":"AdTag_Div1", + "video": { + "w":640, + "h":480, + "mimes": ["video/mp4", "video/x-flv"], + "minduration": 5, + "maxduration": 30, + "startdelay": 5, + "playbackmethod": [1, 3], + "api": [1, 2], + "protocols": [2, 3], + "battr": [13, 14], + "linearity": 1, + "placement": 2, + "minbitrate": 10, + "maxbitrate": 10 + }, + "ext": { + "pmZoneId": "Zone1,Zone2", + "reward": 1 + } + } + ], + "device":{ + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "999" + } + }, + "ext": { + "prebid": {}, + "wrapper": { + "profile": 5123, + "version":1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-video-request", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-video-imp", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["pubmatic.com"], + "crid": "29681110", + "h": 250, + "w": 300, + "dealid":"test deal", + "cat" : ["IAB-1", "IAB-2"], + "ext": { + "dspid": 6, + "deal_channel": 1, + "BidType": 1, + "video" : { + "duration" : 5 + } + } + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-video-imp", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["pubmatic.com"], + "cat": [ + "IAB-1" + ], + "crid": "29681110", + "w": 300, + "h": 250, + "dealid":"test deal", + "ext": { + "dspid": 6, + "deal_channel": 1, + "BidType": 1, + "video" : { + "duration" : 5 + } + } + }, + "type": "video", + "video" :{ + "duration" : 5 + } + } + ] + } + ] + } diff --git a/adapters/pubmatic/pubmatictest/params/race/banner.json b/adapters/pubmatic/pubmatictest/params/race/banner.json new file mode 100644 index 00000000000..86317f86e4c --- /dev/null +++ b/adapters/pubmatic/pubmatictest/params/race/banner.json @@ -0,0 +1,15 @@ +{ + "publisherId": "156209", + "adSlot": "pubmatic_test2@300x250", + "pmzoneid": "drama,sport", + "dctr": "abBucket=4|adType=page|entity=|paidByCategory=|sku=|userLevel=free|platform=android|majorVersion=3.54|version=3.54.0|mobileApplication=true|showId=20166|show=Kisah Untuk Geri|genre=Drama|contentUrl=https://www.iflix.com/title/show/20166|rating=TV-MA|contentLanguage=id", + "keywords": { + "pmzoneid": "Zone1,Zone2", + "dctr": "abBucket=4|adType=page|entity=|paidByCategory=|sku=|userLevel=free|platform=android|majorVersion=3.54|version=3.54.0|mobileApplication=true|showId=20166|show=Kisah Untuk Geri|genre=Drama|contentUrl=https://www.iflix.com/title/show/20166|rating=TV-MA|contentLanguage=id", + "preference": "sports,movies" + }, + "wrapper": { + "version": 2, + "profile": 595 + } +} diff --git a/adapters/pubmatic/pubmatictest/params/race/video.json b/adapters/pubmatic/pubmatictest/params/race/video.json new file mode 100644 index 00000000000..86317f86e4c --- /dev/null +++ b/adapters/pubmatic/pubmatictest/params/race/video.json @@ -0,0 +1,15 @@ +{ + "publisherId": "156209", + "adSlot": "pubmatic_test2@300x250", + "pmzoneid": "drama,sport", + "dctr": "abBucket=4|adType=page|entity=|paidByCategory=|sku=|userLevel=free|platform=android|majorVersion=3.54|version=3.54.0|mobileApplication=true|showId=20166|show=Kisah Untuk Geri|genre=Drama|contentUrl=https://www.iflix.com/title/show/20166|rating=TV-MA|contentLanguage=id", + "keywords": { + "pmzoneid": "Zone1,Zone2", + "dctr": "abBucket=4|adType=page|entity=|paidByCategory=|sku=|userLevel=free|platform=android|majorVersion=3.54|version=3.54.0|mobileApplication=true|showId=20166|show=Kisah Untuk Geri|genre=Drama|contentUrl=https://www.iflix.com/title/show/20166|rating=TV-MA|contentLanguage=id", + "preference": "sports,movies" + }, + "wrapper": { + "version": 2, + "profile": 595 + } +} diff --git a/adapters/pubmatic/pubmatictest/supplemental/app.json b/adapters/pubmatic/pubmatictest/supplemental/app.json index 3a2f7a1fb7a..4f6e810e2a9 100644 --- a/adapters/pubmatic/pubmatictest/supplemental/app.json +++ b/adapters/pubmatic/pubmatictest/supplemental/app.json @@ -27,6 +27,10 @@ "version": 1, "profile": 5123 } + }, + "skadn": { + "skadnetids": ["k674qkevps.skadnetwork"], + "version": "2.0" } } }], @@ -64,7 +68,11 @@ "bidfloor": 0.12, "ext": { "pmZoneId": "Zone1,Zone2", - "preference": "sports,movies" + "preference": "sports,movies", + "skadn": { + "skadnetids": ["k674qkevps.skadnetwork"], + "version": "2.0" + } } } ], @@ -103,7 +111,21 @@ "crid": "29681110", "h": 250, "w": 300, - "dealid":"test deal" + "dealid":"test deal", + "ext": { + "dspid": 6, + "deal_channel": 1, + "skadn": { + "signature": "MDUCGQDreBN5/xBN547tJeUdqcMSBtBA+Lk06b8CGFkjR1V56rh/H9osF8iripkuZApeDsZ+lQ==", + "campaign": "4", + "network": "k674qkevps.skadnetwork", + "nonce": "D0EC0F04-A4BF-445B-ADF1-E010430C29FD", + "timestamp": "1596695461984", + "sourceapp": "525463029", + "itunesitem": "1499436635", + "version": "2.0" + } + } }] } ], @@ -129,17 +151,25 @@ "crid": "29681110", "w": 300, "h": 250, - "dealid":"test deal" + "dealid":"test deal", + "ext": { + "dspid": 6, + "deal_channel": 1, + "skadn": { + "signature": "MDUCGQDreBN5/xBN547tJeUdqcMSBtBA+Lk06b8CGFkjR1V56rh/H9osF8iripkuZApeDsZ+lQ==", + "campaign": "4", + "network": "k674qkevps.skadnetwork", + "nonce": "D0EC0F04-A4BF-445B-ADF1-E010430C29FD", + "timestamp": "1596695461984", + "sourceapp": "525463029", + "itunesitem": "1499436635", + "version": "2.0" + } + } }, "type": "banner" } ] } - ], - "expectedMakeBidsErrors": [ - { - "value": "unexpected end of JSON input", - "comparison": "literal" - } ] - } \ No newline at end of file + } diff --git a/adapters/pubmatic/pubmatictest/supplemental/banner-video.json b/adapters/pubmatic/pubmatictest/supplemental/banner-video.json new file mode 100644 index 00000000000..4c43fcaf815 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/banner-video.json @@ -0,0 +1,129 @@ +{ + "mockBidRequest": { + "id": "multiple-media-request", + "imp": [ + { + "id": "multiple-media-imp", + "video": { + "mimes": ["video/mp4"] + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, + { + "w": 728, + "h": 90 + }] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@0x0", + "publisherId": "999" + } + } + } + ], + "site": { + "id": "siteID" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + "body": { + "id": "multiple-media-request", + "imp": [ + { + "id": "multiple-media-imp", + "tagid":"AdTag_Div1", + "video": { + "mimes": ["video/mp4"] + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 728, + "h": 90 + } + ], + "h": 250, + "w": 300 + } + } + ], + "site": { + "id": "siteID", + "publisher": { + "id": "999" + } + }, + "ext":{"prebid": {}} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "multiple-media-request", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "multiple-media-imp", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["pubmatic.com"], + "crid": "29681110", + "h": 250, + "w": 300, + "dealid":"test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "multiple-media-imp", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["pubmatic.com"], + "crid": "29681110", + "w": 300, + "h": 250, + "dealid":"test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/pubmatic/pubmatictest/supplemental/bidExtMeta.json b/adapters/pubmatic/pubmatictest/supplemental/bidExtMeta.json new file mode 100644 index 00000000000..7ec552d6383 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/bidExtMeta.json @@ -0,0 +1,179 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "publisherId": "999", + "wrapper": { + "version": 1, + "profile": 5123 + }, + "dctr": "k1=v1|k2=v2" + }, + "data": { + "adserver": { + "name": "gam", + "adslot": "/1111/home" + }, + "pbadslot": "/2222/home", + "sport": [ + "rugby", + "cricket" + ] + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "AdTag_Div1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "h": 250, + "w": 300 + }, + "ext": { + "dfp_ad_unit_code": "/1111/home", + "key_val": "k1=v1|k2=v2|sport=rugby,cricket" + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "999" + } + }, + "ext": { + "prebid": {}, + "wrapper": { + "profile": 5123, + "version": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "pubmatic.com" + ], + "crid": "29681110", + "cat":[ + "IAB-1", + "IAB-2" + ], + "h": 250, + "w": 300, + "dealid": "test deal", + "ext": { + "dspid": 6, + "advid": 125, + "deal_channel": 1, + "BidType":0 + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "pubmatic.com" + ], + "crid": "29681110", + "cat":[ + "IAB-1" + ], + "w": 300, + "h": 250, + "dealid": "test deal", + "ext": { + "dspid": 6, + "advid": 125, + "deal_channel": 1, + "BidType":0 + } + }, + "type": "banner", + "meta": { + "advertiserId": 958, + "agencyId": 958, + "networkId": 6, + "demandSource": "6", + "primaryCatId": "IAB-1", + "secondaryCatIds": [ + "IAB-1", + "IAB-2" + ], + "mediaType":"banner" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pubmatic/pubmatictest/supplemental/eid.json b/adapters/pubmatic/pubmatictest/supplemental/eid.json new file mode 100644 index 00000000000..88f82f1cc62 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/eid.json @@ -0,0 +1,223 @@ +{ + "mockBidRequest": { + "id": "test-request-eid-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "publisherId": "999", + "keywords": [{ + "key": "pmZoneID", + "value": ["Zone1", "Zone2"] + }, + { + "key": "preference", + "value": ["sports", "movies"] + } + ], + "kadfloor": "0.12", + "wrapper": { + "version": 1, + "profile": 5123 + } + } + } + }], + "device":{ + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "ext": { + "prebid": { + "bidderparams": { + "acat": ["drg","dlu","ssr"], + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" + } + } + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + }, + "user": { + "ext": { + "eids": [ + { + "source": "bvod.connect", + "uids": [ + { + "atype": 501, + "id": "OztamSession-123456" + }, + { + "ext": { + "demgid": "1234", + "seq": 1 + }, + "atype": 2, + "id": "7D92078A-8246-4BA4-AE5B-76104861E7DC" + }, + { + "ext": { + "demgid": "2345", + "seq": 2 + }, + "atype": 2, + "id": "8D92078A-8246-4BA4-AE5B-76104861E7DC" + } + ] + } + ] + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + "body": { + "id": "test-request-eid-id", + "imp": [ + { + "id": "test-imp-id", + "tagid":"AdTag_Div1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "h": 250, + "w": 300 + }, + "bidfloor": 0.12, + "ext": { + "pmZoneId": "Zone1,Zone2", + "preference": "sports,movies" + } + } + ], + "device":{ + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "999" + } + }, + "user": { + "eids": [ + { + "source": "bvod.connect", + "uids": [ + { + "atype": 501, + "id": "OztamSession-123456" + }, + { + "ext": { + "demgid": "1234", + "seq": 1 + }, + "atype": 2, + "id": "7D92078A-8246-4BA4-AE5B-76104861E7DC" + }, + { + "ext": { + "demgid": "2345", + "seq": 2 + }, + "atype": 2, + "id": "8D92078A-8246-4BA4-AE5B-76104861E7DC" + } + ] + } + ], + "ext":{} + }, + "ext": { + "prebid": { + "bidderparams": { + "acat": ["drg","dlu","ssr"], + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" + } + }, + "wrapper": { + "profile": 5123, + "version":1, + "wiid" : "dwzafakjflan-tygannnvlla-mlljvj" + }, + "acat": ["drg","dlu","ssr"] + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["pubmatic.com"], + "crid": "29681110", + "h": 250, + "w": 300, + "dealid":"test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["pubmatic.com"], + "crid": "29681110", + "w": 300, + "h": 250, + "dealid":"test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json index f4891d90458..ce516b6841c 100644 --- a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json +++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json @@ -55,6 +55,13 @@ "publisher": { "id": "1234" } + }, + "ext": { + "prebid": { + "bidderparams": { + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" + } + } } }, "httpCalls": [ @@ -96,9 +103,14 @@ "ext": { "wrapper": { "profile": 5123, - "version": 1 + "version": 1, + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" }, - "prebid": {} + "prebid": { + "bidderparams": { + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" + } + } } } }, diff --git a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json index 90df5a58fc7..437efcec094 100644 --- a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json +++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json @@ -51,6 +51,13 @@ "publisher": { "id": "1234" } + }, + "ext": { + "prebid": { + "bidderparams": { + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" + } + } } }, "httpCalls": [ @@ -92,9 +99,14 @@ "ext": { "wrapper": { "profile": 5123, - "version": 1 + "version": 1, + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" }, - "prebid": {} + "prebid": { + "bidderparams": { + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" + } + } } } }, @@ -161,4 +173,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/adapters/pubmatic/pubmatictest/supplemental/multiformat.json b/adapters/pubmatic/pubmatictest/supplemental/multiformat.json new file mode 100644 index 00000000000..2747f4b48d1 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/multiformat.json @@ -0,0 +1,135 @@ +{ + "mockBidRequest": { + "id": "multiple-media-request", + "imp": [ + { + "id": "multiple-media-imp", + "video": { + "mimes": ["video/mp4"] + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, + { + "w": 728, + "h": 90 + }] + }, + "native": { + "request": "{\"assets\":[{\"id\":1,\"img\":{\"ext\":{\"image1\":\"image2\"},\"h\": 250,\"mimes\":[\"image\/gif\",\"image\/png\"],\"type\":3,\"w\":300},\"required\":1}]}" + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@1x1", + "publisherId": "999" + } + } + } + ], + "site": { + "id": "siteID" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + "body": { + "id": "multiple-media-request", + "imp": [ + { + "id": "multiple-media-imp", + "tagid":"AdTag_Div1", + "video": { + "mimes": ["video/mp4"] + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 728, + "h": 90 + } + ], + "h": 250, + "w": 300 + }, + "native": { + "request": "{\"assets\":[{\"id\":1,\"img\":{\"ext\":{\"image1\":\"image2\"},\"h\": 250,\"mimes\":[\"image\/gif\",\"image\/png\"],\"type\":3,\"w\":300},\"required\":1}]}" + } + } + ], + "site": { + "id": "siteID", + "publisher": { + "id": "999" + } + }, + "ext":{"prebid": {}} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "multiple-media-request", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "multiple-media-imp", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["pubmatic.com"], + "crid": "29681110", + "h": 250, + "w": 300, + "dealid":"test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "multiple-media-imp", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["pubmatic.com"], + "crid": "29681110", + "w": 300, + "h": 250, + "dealid":"test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/pubmatic/pubmatictest/supplemental/pmZoneIDInKeywords.json b/adapters/pubmatic/pubmatictest/supplemental/pmZoneIDInKeywords.json index 660ec82dfb3..4a201b7da1b 100644 --- a/adapters/pubmatic/pubmatictest/supplemental/pmZoneIDInKeywords.json +++ b/adapters/pubmatic/pubmatictest/supplemental/pmZoneIDInKeywords.json @@ -19,7 +19,7 @@ "dctr": "key1=V1,V2,V3|key2=v1|key3=v3,v5", "keywords": [ { - "key": "pmZoneID", + "key": "pmzoneid", "value": [ "Zone1", "Zone2" diff --git a/adapters/pubmatic/pubmatictest/supplemental/urlEncodedDCTR.json b/adapters/pubmatic/pubmatictest/supplemental/urlEncodedDCTR.json new file mode 100644 index 00000000000..2f4d04bb702 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/urlEncodedDCTR.json @@ -0,0 +1,167 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "publisherId": " 999 ", + "keywords": [ + { + "key": "pmZoneID", + "value": [ + "Zone1", + "Zone2" + ] + }, + { + "key": "preference", + "value": [ + "sports", + "movies" + ] + }, + { + "key":"dctr", + "value":[ + "title%3DThe%20Hunt%7Cgenre%3Danimation%2Cadventure" + ] + } + ], + "wrapper": { + "version": 1, + "profile": 5123 + } + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "AdTag_Div1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "h": 250, + "w": 300 + }, + "ext": { + "key_val": "title=The Hunt|genre=animation,adventure", + "pmZoneId": "Zone1,Zone2", + "preference": "sports,movies" + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "999" + } + }, + "ext": { + "prebid": {}, + "wrapper": { + "profile": 5123, + "version": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "pubmatic.com" + ], + "crid": "29681110", + "h": 250, + "w": 300, + "dealid": "test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "pubmatic.com" + ], + "crid": "29681110", + "w": 300, + "h": 250, + "dealid": "test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/spotx/params_test.go b/adapters/spotx/params_test.go new file mode 100644 index 00000000000..9e42987ef54 --- /dev/null +++ b/adapters/spotx/params_test.go @@ -0,0 +1,58 @@ +package spotx + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestSpotxParams(t *testing.T) { + testValidParams(t) + testInvalidParams(t) +} + +func testValidParams(t *testing.T) { + + params := []string{ + `{"channel_id": "12345", "ad_unit": "instream"}`, + `{"channel_id": "12345", "ad_unit": "instream", "secure": true}`, + `{"channel_id": "12345", "ad_unit": "instream", "secure": true, "ad_volume": 0.4}`, + `{"channel_id": "12345", "ad_unit": "instream", "secure": true, "ad_volume": 0.4, "price_floor": 10}`, + `{"channel_id": "12345", "ad_unit": "instream", "secure": true, "ad_volume": 0.4, "price_floor": 10, "hide_skin": false}`, + } + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Error loading json schema for spotx paramaters: %v", err) + } + + for _, param := range params { + if err := validator.Validate(openrtb_ext.BidderSpotX, json.RawMessage(param)); err != nil { + t.Errorf("Params schema mismatch - %s: %v", param, err) + } + } +} + +// TestInvalidParams makes sure that the 33Across schema rejects all the imp.ext fields we don't support. +func testInvalidParams(t *testing.T) { + params := []string{ + `{"channel_id": "1234", "ad_unit": "instream", "secure": true, "ad_volume": 0.4, "price_floor": 10, "hide_skin": false}`, + `{"channel_id": "12345", "ad_unit": "outstream1", "secure": true, "ad_volume": 0.4, "price_floor": 10, "hide_skin": false}`, + `{"ad_unit": "instream", "secure": true, "ad_volume": 0.4, "price_floor": 10, "hide_skin": false}`, + `{"channel_id": "12345", "secure": true, "ad_volume": 0.4, "price_floor": 10, "hide_skin": false}`, + `{"channel_id": "12345", "ad_unit": "instream", "secure": 1, "ad_volume": 0.4, "price_floor": 10, "hide_skin": false}`, + `{"channel_id": "12345", "ad_unit": "instream", "secure": true, "ad_volume": "0.4", "price_floor": 10, "hide_skin": false}`, + `{"channel_id": "12345", "ad_unit": "instream", "secure": true, "ad_volume": 0.4, "price_floor": 10.12, "hide_skin": false}`, + `{"channel_id": "12345", "ad_unit": "instream", "secure": true, "ad_volume": 0.4, "price_floor": 10, "hide_skin": 0}`, + } + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Error loading json schema for spotx paramaters: %v", err) + } + + for _, param := range params { + if err := validator.Validate(openrtb_ext.BidderSpotX, json.RawMessage(param)); err == nil { + t.Errorf("Unexpexted params schema match - %s", param) + } + } +} diff --git a/adapters/spotx/spotx.go b/adapters/spotx/spotx.go new file mode 100644 index 00000000000..2ec8c32c935 --- /dev/null +++ b/adapters/spotx/spotx.go @@ -0,0 +1,179 @@ +package spotx + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type Adapter struct { + url string +} + +func (a *Adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + var adapterRequests []*adapters.RequestData + + if len(request.Imp) == 0 { + errs = append(errs, &errortypes.BadInput{Message: "No impression in the bid request"}) + return nil, errs + } + + for i, imp := range request.Imp { + if imp.Video == nil { + errs = append(errs, errors.New(fmt.Sprintf("non video impression at index %d", i))) + continue + } + + adapterReq, err := makeRequest(a, request, imp) + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + errs = append(errs, err...) + } + + return adapterRequests, errs +} + +func makeRequest(a *Adapter, originalReq *openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, []error) { + var errs []error + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + return &adapters.RequestData{}, errs + } + + var spotxExt openrtb_ext.ExtImpSpotX + if err := json.Unmarshal(bidderExt.Bidder, &spotxExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + return &adapters.RequestData{}, errs + } + + reqCopy := *originalReq + reqCopy.ID = spotxExt.ChannelID + + intermediateReq, _ := json.Marshal(reqCopy) + reqMap := make(map[string]interface{}) + _ = json.Unmarshal(intermediateReq, &reqMap) + + intermediateImp, _ := json.Marshal(imp) + impMap := make(map[string]interface{}) + _ = json.Unmarshal(intermediateImp, &impMap) + + if spotxExt.Secure { + impMap["secure"] = 1 + } else { + impMap["secure"] = 0 + } + + impVideoExt := map[string]interface{}{} + if impMap["video"].(map[string]interface{})["ext"] != nil { + _ = json.Unmarshal(impMap["video"].(map[string]interface{})["ext"].([]byte), &impVideoExt) + } + impVideoExt["ad_volume"] = spotxExt.AdVolume + impVideoExt["ad_unit"] = spotxExt.AdUnit + if spotxExt.HideSkin { + impVideoExt["hide_skin"] = 1 + } else { + impVideoExt["hide_skin"] = 0 + } + impMap["video"].(map[string]interface{})["ext"] = impVideoExt + impMap["bidfloor"] = float64(spotxExt.PriceFloor) + + // remove bidder from imp.Ext + if bidderExt.Prebid != nil { + byteExt, _ := json.Marshal(bidderExt) + impMap["ext"] = byteExt + } else { + delete(impMap, "ext") + } + reqMap["imp"] = impMap + + reqJSON, err := json.Marshal(reqMap) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + return &adapters.RequestData{ + Method: "POST", + Uri: fmt.Sprintf("%s/%s", a.url, spotxExt.ChannelID), + Body: reqJSON, //TODO: This is a custom request struct, other adapters are sending this openrtb2.BidRequest + Headers: headers, + }, errs +} + +func (a *Adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + if mediaType, err := getMediaTypeForImp(bidResp.ID, internalRequest.Imp); err != nil { + bid := sb.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: mediaType, + }) + } + } + } + return bidResponse, nil +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID && imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + } + return "", errors.New("only videos supported") +} + +func NewSpotxBidder(url string) *Adapter { + return &Adapter{ + url: url, + } +} + +// Builder builds a new instance of the Sovrn adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + bidder := &Adapter{ + url: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/spotx/spotx_test.go b/adapters/spotx/spotx_test.go new file mode 100644 index 00000000000..a60ac89ee81 --- /dev/null +++ b/adapters/spotx/spotx_test.go @@ -0,0 +1,66 @@ +package spotx + +import ( + "encoding/json" + "testing" + + "github.com/magiconair/properties/assert" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" +) + +func TestSpotxMakeBid(t *testing.T) { + + var secure int8 = 1 + + parmsJSON := []byte(`{ + "bidder": { + "channel_id": "85394", + "ad_unit": "instream", + "secure": true, + "ad_volume": 0.800000, + "price_floor": 9, + "hide_skin": false + } + }`) + + request := &openrtb2.BidRequest{ + ID: "1559039248176", + Imp: []openrtb2.Imp{ + { + ID: "28635736ddc2bb", + Video: &openrtb2.Video{ + MIMEs: []string{"video/3gpp"}, + }, + Secure: &secure, + Exp: 2, + Ext: parmsJSON, + }, + }, + } + + extReq := adapters.ExtraRequestInfo{} + reqData, err := NewSpotxBidder("https://search.spotxchange.com/openrtb/2.3/dados").MakeRequests(request, &extReq) + if err != nil { + t.Error("Some err occurred while forming request") + t.FailNow() + } + + assert.Equal(t, reqData[0].Method, "POST") + assert.Equal(t, reqData[0].Uri, "https://search.spotxchange.com/openrtb/2.3/dados/85394") + assert.Equal(t, reqData[0].Headers.Get("Content-Type"), "application/json;charset=utf-8") + + var bodyMap map[string]interface{} + _ = json.Unmarshal(reqData[0].Body, &bodyMap) + assert.Equal(t, bodyMap["id"].(string), "85394") + + impMap := bodyMap["imp"].(map[string]interface{}) + assert.Equal(t, impMap["bidfloor"].(float64), float64(9)) + assert.Equal(t, impMap["secure"].(float64), float64(1)) + + extMap := impMap["video"].(map[string]interface{})["ext"].(map[string]interface{}) + assert.Equal(t, extMap["ad_unit"], "instream") + assert.Equal(t, extMap["ad_volume"], 0.8) + assert.Equal(t, extMap["hide_skin"].(float64), float64(0)) + +} diff --git a/adapters/vastbidder/bidder_macro.go b/adapters/vastbidder/bidder_macro.go new file mode 100644 index 00000000000..cfd95669105 --- /dev/null +++ b/adapters/vastbidder/bidder_macro.go @@ -0,0 +1,1344 @@ +package vastbidder + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/buger/jsonparser" + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// BidderMacro default implementation +type BidderMacro struct { + IBidderMacro + + //Configuration Parameters + Conf *config.Adapter + + //OpenRTB Specific Parameters + Request *openrtb2.BidRequest + IsApp bool + HasGeo bool + Imp *openrtb2.Imp + Publisher *openrtb2.Publisher + Content *openrtb2.Content + + //Extensions + ImpBidderExt openrtb_ext.ExtImpVASTBidder + VASTTag *openrtb_ext.ExtImpVASTBidderTag + UserExt *openrtb_ext.ExtUser + RegsExt *openrtb_ext.ExtRegs + DeviceExt *openrtb_ext.ExtDevice + + //Impression level Request Headers + ImpReqHeaders http.Header + + //Key-Values Map + KV map[string]any +} + +// NewBidderMacro contains definition for all openrtb macro's +func NewBidderMacro() IBidderMacro { + obj := &BidderMacro{} + obj.IBidderMacro = obj + return obj +} + +func (tag *BidderMacro) init() { + if nil != tag.Request.App { + tag.IsApp = true + tag.Publisher = tag.Request.App.Publisher + tag.Content = tag.Request.App.Content + } else { + tag.Publisher = tag.Request.Site.Publisher + tag.Content = tag.Request.Site.Content + } + tag.HasGeo = nil != tag.Request.Device && nil != tag.Request.Device.Geo + + //Read User Extensions + if nil != tag.Request.User && nil != tag.Request.User.Ext { + var ext openrtb_ext.ExtUser + err := json.Unmarshal(tag.Request.User.Ext, &ext) + if nil == err { + tag.UserExt = &ext + } + } + + //Read Regs Extensions + if nil != tag.Request.Regs && nil != tag.Request.Regs.Ext { + var ext openrtb_ext.ExtRegs + err := json.Unmarshal(tag.Request.Regs.Ext, &ext) + if nil == err { + tag.RegsExt = &ext + } + } + + //Read Device Extensions + if nil != tag.Request.Device && nil != tag.Request.Device.Ext { + var ext openrtb_ext.ExtDevice + err := json.Unmarshal(tag.Request.Device.Ext, &ext) + if nil == err { + tag.DeviceExt = &ext + } + } + if tag.Request != nil && tag.Request.Ext != nil { + keyval, _, _, err := jsonparser.Get(tag.Request.Ext, prebid, keyval) + if err != nil { + return + } + var kv map[string]any + err = json.Unmarshal(keyval, &kv) + if err != nil { + return + } + tag.KV = kv + } + +} + +// InitBidRequest will initialise BidRequest +func (tag *BidderMacro) InitBidRequest(request *openrtb2.BidRequest) { + tag.Request = request + tag.init() +} + +// LoadImpression will set current imp +func (tag *BidderMacro) LoadImpression(imp *openrtb2.Imp) (*openrtb_ext.ExtImpVASTBidder, error) { + tag.Imp = imp + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, err + } + + tag.ImpBidderExt = openrtb_ext.ExtImpVASTBidder{} + if err := json.Unmarshal(bidderExt.Bidder, &tag.ImpBidderExt); err != nil { + return nil, err + } + return &tag.ImpBidderExt, nil +} + +// LoadVASTTag will set current VAST Tag details in bidder keys +func (tag *BidderMacro) LoadVASTTag(vastTag *openrtb_ext.ExtImpVASTBidderTag) { + tag.VASTTag = vastTag +} + +// GetBidderKeys will set bidder level keys +func (tag *BidderMacro) GetBidderKeys() map[string]string { + //Adding VAST Tag Bidder Parameters + keys := NormalizeJSON(tag.VASTTag.Params) + + //Adding VAST Tag Standard Params + keys["dur"] = strconv.Itoa(tag.VASTTag.Duration) + + //Adding Headers as Custom Macros + + //Adding Cookies as Custom Macros + + //Adding Default Empty for standard keys + for i := range ParamKeys { + if _, ok := keys[ParamKeys[i]]; !ok { + keys[ParamKeys[i]] = "" + } + } + + return keys +} + +// SetAdapterConfig will set Adapter config +func (tag *BidderMacro) SetAdapterConfig(conf *config.Adapter) { + tag.Conf = conf +} + +// GetURI get URL +func (tag *BidderMacro) GetURI() string { + + //check for URI at impression level + if nil != tag.VASTTag { + return tag.VASTTag.URL + } + + //check for URI at config level + return tag.Conf.Endpoint +} + +// GetHeaders returns list of custom request headers +// Override this method if your Vast bidder needs custom request headers +func (tag *BidderMacro) GetHeaders() http.Header { + return http.Header{} +} + +// GetValue returns the value for given key +// isKeyFound will check the key is present or not +func (tag *BidderMacro) GetValue(key string) (string, bool) { + macroKeys := strings.Split(key, ".") + isKeyFound := false + + // This will check if key has prefix kv/kvm + // if prefix present it will always return isKeyFound as true as it will help to replace the key with empty string in VAST TAG + if (macroKeys[0] == MacroKV || macroKeys[0] == MacroKVM) && len(macroKeys) > 1 { + isKeyFound = true + if tag.KV == nil { + return "", isKeyFound + } + switch macroKeys[0] { + case MacroKV: + val := getValueFromMap(macroKeys[1:], tag.KV) + if dataMap, ok := val.(map[string]interface{}); ok { + return mapToQuery(dataMap), isKeyFound + } + return fmt.Sprintf("%v", val), isKeyFound + case MacroKVM: + val := getValueFromMap(macroKeys[1:], tag.KV) + if isMap(val) { + return getJSONString(val), isKeyFound + } + return fmt.Sprintf("%v", val), isKeyFound + } + } + return "", isKeyFound +} + +/********************* Request *********************/ + +// MacroTest contains definition for Test Parameter +func (tag *BidderMacro) MacroTest(key string) string { + if tag.Request.Test > 0 { + return strconv.Itoa(int(tag.Request.Test)) + } + return "" +} + +// MacroTimeout contains definition for Timeout Parameter +func (tag *BidderMacro) MacroTimeout(key string) string { + if tag.Request.TMax > 0 { + return strconv.FormatInt(tag.Request.TMax, intBase) + } + return "" +} + +// MacroWhitelistSeat contains definition for WhitelistSeat Parameter +func (tag *BidderMacro) MacroWhitelistSeat(key string) string { + return strings.Join(tag.Request.WSeat, comma) +} + +// MacroWhitelistLang contains definition for WhitelistLang Parameter +func (tag *BidderMacro) MacroWhitelistLang(key string) string { + return strings.Join(tag.Request.WLang, comma) +} + +// MacroBlockedSeat contains definition for Blockedseat Parameter +func (tag *BidderMacro) MacroBlockedSeat(key string) string { + return strings.Join(tag.Request.BSeat, comma) +} + +// MacroCurrency contains definition for Currency Parameter +func (tag *BidderMacro) MacroCurrency(key string) string { + return strings.Join(tag.Request.Cur, comma) +} + +// MacroBlockedCategory contains definition for BlockedCategory Parameter +func (tag *BidderMacro) MacroBlockedCategory(key string) string { + return strings.Join(tag.Request.BCat, comma) +} + +// MacroBlockedAdvertiser contains definition for BlockedAdvertiser Parameter +func (tag *BidderMacro) MacroBlockedAdvertiser(key string) string { + return strings.Join(tag.Request.BAdv, comma) +} + +// MacroBlockedApp contains definition for BlockedApp Parameter +func (tag *BidderMacro) MacroBlockedApp(key string) string { + return strings.Join(tag.Request.BApp, comma) +} + +/********************* Source *********************/ + +// MacroFD contains definition for FD Parameter +func (tag *BidderMacro) MacroFD(key string) string { + if nil != tag.Request.Source { + return strconv.Itoa(int(tag.Request.Source.FD)) + } + return "" +} + +// MacroTransactionID contains definition for TransactionID Parameter +func (tag *BidderMacro) MacroTransactionID(key string) string { + if nil != tag.Request.Source { + return tag.Request.Source.TID + } + return "" +} + +// MacroPaymentIDChain contains definition for PaymentIDChain Parameter +func (tag *BidderMacro) MacroPaymentIDChain(key string) string { + if nil != tag.Request.Source { + return tag.Request.Source.PChain + } + return "" +} + +// MacroSchain contains definition for Schain Parameter +func (tag *BidderMacro) MacroSchain(key string) string { + if tag.Request.Source == nil { + return "" + } + + if tag.Request.Source.SChain != nil { + return openrtb_ext.SerializeSupplyChain(tag.Request.Source.SChain) + } + + if tag.Request.Source.Ext != nil { + schain, _, _, err := jsonparser.Get(tag.Request.Source.Ext, MacroSchain) + + if err != nil { + return "" + } + var schainObj openrtb2.SupplyChain + err = json.Unmarshal(schain, &schainObj) + + if err != nil { + return "" + } + return openrtb_ext.SerializeSupplyChain(&schainObj) + } + return "" +} + +/********************* Regs *********************/ + +// MacroCoppa contains definition for Coppa Parameter +func (tag *BidderMacro) MacroCoppa(key string) string { + if nil != tag.Request.Regs { + return strconv.Itoa(int(tag.Request.Regs.COPPA)) + } + return "" +} + +/********************* Impression *********************/ + +// MacroDisplayManager contains definition for DisplayManager Parameter +func (tag *BidderMacro) MacroDisplayManager(key string) string { + return tag.Imp.DisplayManager +} + +// MacroDisplayManagerVersion contains definition for DisplayManagerVersion Parameter +func (tag *BidderMacro) MacroDisplayManagerVersion(key string) string { + return tag.Imp.DisplayManagerVer +} + +// MacroInterstitial contains definition for Interstitial Parameter +func (tag *BidderMacro) MacroInterstitial(key string) string { + if tag.Imp.Instl > 0 { + return strconv.Itoa(int(tag.Imp.Instl)) + } + return "" +} + +// MacroTagID contains definition for TagID Parameter +func (tag *BidderMacro) MacroTagID(key string) string { + return tag.Imp.TagID +} + +// MacroBidFloor contains definition for BidFloor Parameter +func (tag *BidderMacro) MacroBidFloor(key string) string { + if tag.Imp.BidFloor > 0 { + return fmt.Sprintf("%g", tag.Imp.BidFloor) + } + return "" +} + +// MacroBidFloorCurrency contains definition for BidFloorCurrency Parameter +func (tag *BidderMacro) MacroBidFloorCurrency(key string) string { + return tag.Imp.BidFloorCur +} + +// MacroSecure contains definition for Secure Parameter +func (tag *BidderMacro) MacroSecure(key string) string { + if nil != tag.Imp.Secure { + return strconv.Itoa(int(*tag.Imp.Secure)) + } + return "" +} + +// MacroPMP contains definition for PMP Parameter +func (tag *BidderMacro) MacroPMP(key string) string { + if nil != tag.Imp.PMP { + data, _ := json.Marshal(tag.Imp.PMP) + return string(data) + } + return "" +} + +/********************* Video *********************/ + +// MacroVideoMIMES contains definition for VideoMIMES Parameter +func (tag *BidderMacro) MacroVideoMIMES(key string) string { + if nil != tag.Imp.Video { + return strings.Join(tag.Imp.Video.MIMEs, comma) + } + return "" +} + +// MacroVideoMinimumDuration contains definition for VideoMinimumDuration Parameter +func (tag *BidderMacro) MacroVideoMinimumDuration(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.MinDuration > 0 { + return strconv.FormatInt(tag.Imp.Video.MinDuration, intBase) + } + return "" +} + +// MacroVideoMaximumDuration contains definition for VideoMaximumDuration Parameter +func (tag *BidderMacro) MacroVideoMaximumDuration(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.MaxDuration > 0 { + return strconv.FormatInt(tag.Imp.Video.MaxDuration, intBase) + } + return "" +} + +// MacroVideoProtocols contains definition for VideoProtocols Parameter +func (tag *BidderMacro) MacroVideoProtocols(key string) string { + if nil != tag.Imp.Video { + value := tag.Imp.Video.Protocols + return ObjectArrayToString(len(value), comma, func(i int) string { + return strconv.FormatInt(int64(value[i]), intBase) + }) + } + return "" +} + +// MacroVideoPlayerWidth contains definition for VideoPlayerWidth Parameter +func (tag *BidderMacro) MacroVideoPlayerWidth(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.W > 0 { + return strconv.FormatInt(int64(tag.Imp.Video.W), intBase) + } + return "" +} + +// MacroVideoPlayerHeight contains definition for VideoPlayerHeight Parameter +func (tag *BidderMacro) MacroVideoPlayerHeight(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.H > 0 { + return strconv.FormatInt(int64(tag.Imp.Video.H), intBase) + } + return "" +} + +// MacroVideoStartDelay contains definition for VideoStartDelay Parameter +func (tag *BidderMacro) MacroVideoStartDelay(key string) string { + if nil != tag.Imp.Video && nil != tag.Imp.Video.StartDelay { + return strconv.FormatInt(int64(*tag.Imp.Video.StartDelay), intBase) + } + return "" +} + +// MacroVideoPlacement contains definition for VideoPlacement Parameter +func (tag *BidderMacro) MacroVideoPlacement(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.Placement > 0 { + return strconv.FormatInt(int64(tag.Imp.Video.Placement), intBase) + } + return "" +} + +// MacroVideoLinearity contains definition for VideoLinearity Parameter +func (tag *BidderMacro) MacroVideoLinearity(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.Linearity > 0 { + return strconv.FormatInt(int64(tag.Imp.Video.Linearity), intBase) + } + return "" +} + +// MacroVideoSkip contains definition for VideoSkip Parameter +func (tag *BidderMacro) MacroVideoSkip(key string) string { + if nil != tag.Imp.Video && nil != tag.Imp.Video.Skip { + return strconv.FormatInt(int64(*tag.Imp.Video.Skip), intBase) + } + return "" +} + +// MacroVideoSkipMinimum contains definition for VideoSkipMinimum Parameter +func (tag *BidderMacro) MacroVideoSkipMinimum(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.SkipMin > 0 { + return strconv.FormatInt(tag.Imp.Video.SkipMin, intBase) + } + return "" +} + +// MacroVideoSkipAfter contains definition for VideoSkipAfter Parameter +func (tag *BidderMacro) MacroVideoSkipAfter(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.SkipAfter > 0 { + return strconv.FormatInt(tag.Imp.Video.SkipAfter, intBase) + } + return "" +} + +// MacroVideoSequence contains definition for VideoSequence Parameter +func (tag *BidderMacro) MacroVideoSequence(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.Sequence > 0 { + return strconv.FormatInt(int64(tag.Imp.Video.Sequence), intBase) + } + return "" +} + +// MacroVideoBlockedAttribute contains definition for VideoBlockedAttribute Parameter +func (tag *BidderMacro) MacroVideoBlockedAttribute(key string) string { + if nil != tag.Imp.Video { + value := tag.Imp.Video.BAttr + return ObjectArrayToString(len(value), comma, func(i int) string { + return strconv.FormatInt(int64(value[i]), intBase) + }) + } + return "" +} + +// MacroVideoMaximumExtended contains definition for VideoMaximumExtended Parameter +func (tag *BidderMacro) MacroVideoMaximumExtended(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.MaxExtended > 0 { + return strconv.FormatInt(tag.Imp.Video.MaxExtended, intBase) + } + return "" +} + +// MacroVideoMinimumBitRate contains definition for VideoMinimumBitRate Parameter +func (tag *BidderMacro) MacroVideoMinimumBitRate(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.MinBitRate > 0 { + return strconv.FormatInt(int64(tag.Imp.Video.MinBitRate), intBase) + } + return "" +} + +// MacroVideoMaximumBitRate contains definition for VideoMaximumBitRate Parameter +func (tag *BidderMacro) MacroVideoMaximumBitRate(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.MaxBitRate > 0 { + return strconv.FormatInt(int64(tag.Imp.Video.MaxBitRate), intBase) + } + return "" +} + +// MacroVideoBoxing contains definition for VideoBoxing Parameter +func (tag *BidderMacro) MacroVideoBoxing(key string) string { + if nil != tag.Imp.Video && tag.Imp.Video.BoxingAllowed > 0 { + return strconv.FormatInt(int64(tag.Imp.Video.BoxingAllowed), intBase) + } + return "" +} + +// MacroVideoPlaybackMethod contains definition for VideoPlaybackMethod Parameter +func (tag *BidderMacro) MacroVideoPlaybackMethod(key string) string { + if nil != tag.Imp.Video { + value := tag.Imp.Video.PlaybackMethod + return ObjectArrayToString(len(value), comma, func(i int) string { + return strconv.FormatInt(int64(value[i]), intBase) + }) + } + return "" +} + +// MacroVideoDelivery contains definition for VideoDelivery Parameter +func (tag *BidderMacro) MacroVideoDelivery(key string) string { + if nil != tag.Imp.Video { + value := tag.Imp.Video.Delivery + return ObjectArrayToString(len(value), comma, func(i int) string { + return strconv.FormatInt(int64(value[i]), intBase) + }) + } + return "" +} + +// MacroVideoPosition contains definition for VideoPosition Parameter +func (tag *BidderMacro) MacroVideoPosition(key string) string { + if nil != tag.Imp.Video && nil != tag.Imp.Video.Pos { + return strconv.FormatInt(int64(*tag.Imp.Video.Pos), intBase) + } + return "" +} + +// MacroVideoAPI contains definition for VideoAPI Parameter +func (tag *BidderMacro) MacroVideoAPI(key string) string { + if nil != tag.Imp.Video { + value := tag.Imp.Video.API + return ObjectArrayToString(len(value), comma, func(i int) string { + return strconv.FormatInt(int64(value[i]), intBase) + }) + } + return "" +} + +/********************* Site *********************/ + +// MacroSiteID contains definition for SiteID Parameter +func (tag *BidderMacro) MacroSiteID(key string) string { + if !tag.IsApp { + return tag.Request.Site.ID + } + return "" +} + +// MacroSiteName contains definition for SiteName Parameter +func (tag *BidderMacro) MacroSiteName(key string) string { + if !tag.IsApp { + return tag.Request.Site.Name + } + return "" +} + +// MacroSitePage contains definition for SitePage Parameter +func (tag *BidderMacro) MacroSitePage(key string) string { + if !tag.IsApp && nil != tag.Request && nil != tag.Request.Site { + return tag.Request.Site.Page + } + return "" +} + +// MacroSiteReferrer contains definition for SiteReferrer Parameter +func (tag *BidderMacro) MacroSiteReferrer(key string) string { + if !tag.IsApp { + return tag.Request.Site.Ref + } + return "" +} + +// MacroSiteSearch contains definition for SiteSearch Parameter +func (tag *BidderMacro) MacroSiteSearch(key string) string { + if !tag.IsApp { + return tag.Request.Site.Search + } + return "" +} + +// MacroSiteMobile contains definition for SiteMobile Parameter +func (tag *BidderMacro) MacroSiteMobile(key string) string { + if !tag.IsApp && tag.Request.Site.Mobile > 0 { + return strconv.FormatInt(int64(tag.Request.Site.Mobile), intBase) + } + return "" +} + +/********************* App *********************/ + +// MacroAppID contains definition for AppID Parameter +func (tag *BidderMacro) MacroAppID(key string) string { + if tag.IsApp { + return tag.Request.App.ID + } + return "" +} + +// MacroAppName contains definition for AppName Parameter +func (tag *BidderMacro) MacroAppName(key string) string { + if tag.IsApp { + return tag.Request.App.Name + } + return "" +} + +// MacroAppBundle contains definition for AppBundle Parameter +func (tag *BidderMacro) MacroAppBundle(key string) string { + if tag.IsApp { + return tag.Request.App.Bundle + } + return "" +} + +// MacroAppStoreURL contains definition for AppStoreURL Parameter +func (tag *BidderMacro) MacroAppStoreURL(key string) string { + if tag.IsApp { + return tag.Request.App.StoreURL + } + return "" +} + +// MacroAppVersion contains definition for AppVersion Parameter +func (tag *BidderMacro) MacroAppVersion(key string) string { + if tag.IsApp { + return tag.Request.App.Ver + } + return "" +} + +// MacroAppPaid contains definition for AppPaid Parameter +func (tag *BidderMacro) MacroAppPaid(key string) string { + if tag.IsApp && tag.Request.App.Paid != 0 { + return strconv.FormatInt(int64(tag.Request.App.Paid), intBase) + } + return "" +} + +/********************* Site/App Common *********************/ + +// MacroCategory contains definition for Category Parameter +func (tag *BidderMacro) MacroCategory(key string) string { + if tag.IsApp { + return strings.Join(tag.Request.App.Cat, comma) + } + return strings.Join(tag.Request.Site.Cat, comma) +} + +// MacroDomain contains definition for Domain Parameter +func (tag *BidderMacro) MacroDomain(key string) string { + if tag.IsApp { + return tag.Request.App.Domain + } + return tag.Request.Site.Domain +} + +// MacroSectionCategory contains definition for SectionCategory Parameter +func (tag *BidderMacro) MacroSectionCategory(key string) string { + if tag.IsApp { + return strings.Join(tag.Request.App.SectionCat, comma) + } + return strings.Join(tag.Request.Site.SectionCat, comma) +} + +// MacroPageCategory contains definition for PageCategory Parameter +func (tag *BidderMacro) MacroPageCategory(key string) string { + if tag.IsApp { + return strings.Join(tag.Request.App.PageCat, comma) + } + return strings.Join(tag.Request.Site.PageCat, comma) +} + +// MacroPrivacyPolicy contains definition for PrivacyPolicy Parameter +func (tag *BidderMacro) MacroPrivacyPolicy(key string) string { + var value int8 = 0 + if tag.IsApp { + value = tag.Request.App.PrivacyPolicy + } else { + value = tag.Request.Site.PrivacyPolicy + } + if value > 0 { + return strconv.FormatInt(int64(value), intBase) + } + return "" +} + +// MacroKeywords contains definition for Keywords Parameter +func (tag *BidderMacro) MacroKeywords(key string) string { + if tag.IsApp { + return tag.Request.App.Keywords + } + return tag.Request.Site.Keywords +} + +/********************* Publisher *********************/ + +// MacroPubID contains definition for PubID Parameter +func (tag *BidderMacro) MacroPubID(key string) string { + if nil != tag.Publisher { + return tag.Publisher.ID + } + return "" +} + +// MacroPubName contains definition for PubName Parameter +func (tag *BidderMacro) MacroPubName(key string) string { + if nil != tag.Publisher { + return tag.Publisher.Name + } + return "" +} + +// MacroPubDomain contains definition for PubDomain Parameter +func (tag *BidderMacro) MacroPubDomain(key string) string { + if nil != tag.Publisher { + return tag.Publisher.Domain + } + return "" +} + +/********************* Content *********************/ + +// MacroContentID contains definition for ContentID Parameter +func (tag *BidderMacro) MacroContentID(key string) string { + if nil != tag.Content { + return tag.Content.ID + } + return "" +} + +// MacroContentEpisode contains definition for ContentEpisode Parameter +func (tag *BidderMacro) MacroContentEpisode(key string) string { + if nil != tag.Content { + return strconv.FormatInt(int64(tag.Content.Episode), intBase) + } + return "" +} + +// MacroContentTitle contains definition for ContentTitle Parameter +func (tag *BidderMacro) MacroContentTitle(key string) string { + if nil != tag.Content { + return tag.Content.Title + } + return "" +} + +// MacroContentSeries contains definition for ContentSeries Parameter +func (tag *BidderMacro) MacroContentSeries(key string) string { + if nil != tag.Content { + return tag.Content.Series + } + return "" +} + +// MacroContentSeason contains definition for ContentSeason Parameter +func (tag *BidderMacro) MacroContentSeason(key string) string { + if nil != tag.Content { + return tag.Content.Season + } + return "" +} + +// MacroContentArtist contains definition for ContentArtist Parameter +func (tag *BidderMacro) MacroContentArtist(key string) string { + if nil != tag.Content { + return tag.Content.Artist + } + return "" +} + +// MacroContentGenre contains definition for ContentGenre Parameter +func (tag *BidderMacro) MacroContentGenre(key string) string { + if nil != tag.Content { + return tag.Content.Genre + } + return "" +} + +// MacroContentAlbum contains definition for ContentAlbum Parameter +func (tag *BidderMacro) MacroContentAlbum(key string) string { + if nil != tag.Content { + return tag.Content.Album + } + return "" +} + +// MacroContentISrc contains definition for ContentISrc Parameter +func (tag *BidderMacro) MacroContentISrc(key string) string { + if nil != tag.Content { + return tag.Content.ISRC + } + return "" +} + +// MacroContentURL contains definition for ContentURL Parameter +func (tag *BidderMacro) MacroContentURL(key string) string { + if nil != tag.Content { + return tag.Content.URL + } + return "" +} + +// MacroContentCategory contains definition for ContentCategory Parameter +func (tag *BidderMacro) MacroContentCategory(key string) string { + if nil != tag.Content { + return strings.Join(tag.Content.Cat, comma) + } + return "" +} + +// MacroContentProductionQuality contains definition for ContentProductionQuality Parameter +func (tag *BidderMacro) MacroContentProductionQuality(key string) string { + if nil != tag.Content && nil != tag.Content.ProdQ { + return strconv.FormatInt(int64(*tag.Content.ProdQ), intBase) + } + return "" +} + +// MacroContentVideoQuality contains definition for ContentVideoQuality Parameter +func (tag *BidderMacro) MacroContentVideoQuality(key string) string { + if nil != tag.Content && nil != tag.Content.VideoQuality { + return strconv.FormatInt(int64(*tag.Content.VideoQuality), intBase) + } + return "" +} + +// MacroContentContext contains definition for ContentContext Parameter +func (tag *BidderMacro) MacroContentContext(key string) string { + if nil != tag.Content && tag.Content.Context > 0 { + return strconv.FormatInt(int64(tag.Content.Context), intBase) + } + return "" +} + +// MacroContentContentRating contains definition for ContentContentRating Parameter +func (tag *BidderMacro) MacroContentContentRating(key string) string { + if nil != tag.Content { + return tag.Content.ContentRating + } + return "" +} + +// MacroContentUserRating contains definition for ContentUserRating Parameter +func (tag *BidderMacro) MacroContentUserRating(key string) string { + if nil != tag.Content { + return tag.Content.UserRating + } + return "" +} + +// MacroContentQAGMediaRating contains definition for ContentQAGMediaRating Parameter +func (tag *BidderMacro) MacroContentQAGMediaRating(key string) string { + if nil != tag.Content && tag.Content.QAGMediaRating > 0 { + return strconv.FormatInt(int64(tag.Content.QAGMediaRating), intBase) + } + return "" +} + +// MacroContentKeywords contains definition for ContentKeywords Parameter +func (tag *BidderMacro) MacroContentKeywords(key string) string { + if nil != tag.Content { + return tag.Content.Keywords + } + return "" +} + +// MacroContentLiveStream contains definition for ContentLiveStream Parameter +func (tag *BidderMacro) MacroContentLiveStream(key string) string { + if nil != tag.Content { + return strconv.FormatInt(int64(tag.Content.LiveStream), intBase) + } + return "" +} + +// MacroContentSourceRelationship contains definition for ContentSourceRelationship Parameter +func (tag *BidderMacro) MacroContentSourceRelationship(key string) string { + if nil != tag.Content { + return strconv.FormatInt(int64(tag.Content.SourceRelationship), intBase) + } + return "" +} + +// MacroContentLength contains definition for ContentLength Parameter +func (tag *BidderMacro) MacroContentLength(key string) string { + if nil != tag.Content { + return strconv.FormatInt(int64(tag.Content.Len), intBase) + } + return "" +} + +// MacroContentLanguage contains definition for ContentLanguage Parameter +func (tag *BidderMacro) MacroContentLanguage(key string) string { + if nil != tag.Content { + return tag.Content.Language + } + return "" +} + +// MacroContentEmbeddable contains definition for ContentEmbeddable Parameter +func (tag *BidderMacro) MacroContentEmbeddable(key string) string { + if nil != tag.Content { + return strconv.FormatInt(int64(tag.Content.Embeddable), intBase) + } + return "" +} + +/********************* Producer *********************/ + +// MacroProducerID contains definition for ProducerID Parameter +func (tag *BidderMacro) MacroProducerID(key string) string { + if nil != tag.Content && nil != tag.Content.Producer { + return tag.Content.Producer.ID + } + return "" +} + +// MacroProducerName contains definition for ProducerName Parameter +func (tag *BidderMacro) MacroProducerName(key string) string { + if nil != tag.Content && nil != tag.Content.Producer { + return tag.Content.Producer.Name + } + return "" +} + +/********************* Device *********************/ + +// MacroUserAgent contains definition for UserAgent Parameter +func (tag *BidderMacro) MacroUserAgent(key string) string { + if nil != tag.Request && nil != tag.Request.Device { + return tag.Request.Device.UA + } + return "" +} + +// MacroDNT contains definition for DNT Parameter +func (tag *BidderMacro) MacroDNT(key string) string { + if nil != tag.Request.Device && nil != tag.Request.Device.DNT { + return strconv.FormatInt(int64(*tag.Request.Device.DNT), intBase) + } + return "" +} + +// MacroLMT contains definition for LMT Parameter +func (tag *BidderMacro) MacroLMT(key string) string { + if nil != tag.Request.Device && nil != tag.Request.Device.Lmt { + return strconv.FormatInt(int64(*tag.Request.Device.Lmt), intBase) + } + return "" +} + +// MacroIP contains definition for IP Parameter +func (tag *BidderMacro) MacroIP(key string) string { + if nil != tag.Request && nil != tag.Request.Device { + if len(tag.Request.Device.IP) > 0 { + return tag.Request.Device.IP + } else if len(tag.Request.Device.IPv6) > 0 { + return tag.Request.Device.IPv6 + } + } + return "" +} + +// MacroDeviceType contains definition for DeviceType Parameter +func (tag *BidderMacro) MacroDeviceType(key string) string { + if nil != tag.Request.Device && tag.Request.Device.DeviceType > 0 { + return strconv.FormatInt(int64(tag.Request.Device.DeviceType), intBase) + } + return "" +} + +// MacroMake contains definition for Make Parameter +func (tag *BidderMacro) MacroMake(key string) string { + if nil != tag.Request.Device { + return tag.Request.Device.Make + } + return "" +} + +// MacroModel contains definition for Model Parameter +func (tag *BidderMacro) MacroModel(key string) string { + if nil != tag.Request.Device { + return tag.Request.Device.Model + } + return "" +} + +// MacroDeviceOS contains definition for DeviceOS Parameter +func (tag *BidderMacro) MacroDeviceOS(key string) string { + if nil != tag.Request.Device { + return tag.Request.Device.OS + } + return "" +} + +// MacroDeviceOSVersion contains definition for DeviceOSVersion Parameter +func (tag *BidderMacro) MacroDeviceOSVersion(key string) string { + if nil != tag.Request.Device { + return tag.Request.Device.OSV + } + return "" +} + +// MacroDeviceWidth contains definition for DeviceWidth Parameter +func (tag *BidderMacro) MacroDeviceWidth(key string) string { + if nil != tag.Request.Device { + return strconv.FormatInt(int64(tag.Request.Device.W), intBase) + } + return "" +} + +// MacroDeviceHeight contains definition for DeviceHeight Parameter +func (tag *BidderMacro) MacroDeviceHeight(key string) string { + if nil != tag.Request.Device { + return strconv.FormatInt(int64(tag.Request.Device.H), intBase) + } + return "" +} + +// MacroDeviceJS contains definition for DeviceJS Parameter +func (tag *BidderMacro) MacroDeviceJS(key string) string { + if nil != tag.Request.Device { + return strconv.FormatInt(int64(tag.Request.Device.JS), intBase) + } + return "" +} + +// MacroDeviceLanguage contains definition for DeviceLanguage Parameter +func (tag *BidderMacro) MacroDeviceLanguage(key string) string { + if nil != tag.Request && nil != tag.Request.Device { + return tag.Request.Device.Language + } + return "" +} + +// MacroDeviceIFA contains definition for DeviceIFA Parameter +func (tag *BidderMacro) MacroDeviceIFA(key string) string { + if nil != tag.Request.Device { + return tag.Request.Device.IFA + } + return "" +} + +// MacroDeviceIFAType contains definition for DeviceIFAType +func (tag *BidderMacro) MacroDeviceIFAType(key string) string { + if nil != tag.DeviceExt { + return tag.DeviceExt.IFAType + } + return "" +} + +// MacroDeviceDIDSHA1 contains definition for DeviceDIDSHA1 Parameter +func (tag *BidderMacro) MacroDeviceDIDSHA1(key string) string { + if nil != tag.Request.Device { + return tag.Request.Device.DIDSHA1 + } + return "" +} + +// MacroDeviceDIDMD5 contains definition for DeviceDIDMD5 Parameter +func (tag *BidderMacro) MacroDeviceDIDMD5(key string) string { + if nil != tag.Request.Device { + return tag.Request.Device.DIDMD5 + } + return "" +} + +// MacroDeviceDPIDSHA1 contains definition for DeviceDPIDSHA1 Parameter +func (tag *BidderMacro) MacroDeviceDPIDSHA1(key string) string { + if nil != tag.Request.Device { + return tag.Request.Device.DPIDSHA1 + } + return "" +} + +// MacroDeviceDPIDMD5 contains definition for DeviceDPIDMD5 Parameter +func (tag *BidderMacro) MacroDeviceDPIDMD5(key string) string { + if nil != tag.Request.Device { + return tag.Request.Device.DPIDMD5 + } + return "" +} + +// MacroDeviceMACSHA1 contains definition for DeviceMACSHA1 Parameter +func (tag *BidderMacro) MacroDeviceMACSHA1(key string) string { + if nil != tag.Request.Device { + return tag.Request.Device.MACSHA1 + } + return "" +} + +// MacroDeviceMACMD5 contains definition for DeviceMACMD5 Parameter +func (tag *BidderMacro) MacroDeviceMACMD5(key string) string { + if nil != tag.Request.Device { + return tag.Request.Device.MACMD5 + } + return "" +} + +/********************* Geo *********************/ + +// MacroLatitude contains definition for Latitude Parameter +func (tag *BidderMacro) MacroLatitude(key string) string { + if tag.HasGeo { + return fmt.Sprintf("%g", tag.Request.Device.Geo.Lat) + } + return "" +} + +// MacroLongitude contains definition for Longitude Parameter +func (tag *BidderMacro) MacroLongitude(key string) string { + if tag.HasGeo { + return fmt.Sprintf("%g", tag.Request.Device.Geo.Lon) + } + return "" +} + +// MacroCountry contains definition for Country Parameter +func (tag *BidderMacro) MacroCountry(key string) string { + if tag.HasGeo { + return tag.Request.Device.Geo.Country + } + return "" +} + +// MacroRegion contains definition for Region Parameter +func (tag *BidderMacro) MacroRegion(key string) string { + if tag.HasGeo { + return tag.Request.Device.Geo.Region + } + return "" +} + +// MacroCity contains definition for City Parameter +func (tag *BidderMacro) MacroCity(key string) string { + if tag.HasGeo { + return tag.Request.Device.Geo.City + } + return "" +} + +// MacroZip contains definition for Zip Parameter +func (tag *BidderMacro) MacroZip(key string) string { + if tag.HasGeo { + return tag.Request.Device.Geo.ZIP + } + return "" +} + +// MacroUTCOffset contains definition for UTCOffset Parameter +func (tag *BidderMacro) MacroUTCOffset(key string) string { + if tag.HasGeo { + return strconv.FormatInt(tag.Request.Device.Geo.UTCOffset, intBase) + } + return "" +} + +/********************* User *********************/ + +// MacroUserID contains definition for UserID Parameter +func (tag *BidderMacro) MacroUserID(key string) string { + if nil != tag.Request.User { + return tag.Request.User.ID + } + return "" +} + +// MacroYearOfBirth contains definition for YearOfBirth Parameter +func (tag *BidderMacro) MacroYearOfBirth(key string) string { + if nil != tag.Request.User && tag.Request.User.Yob > 0 { + return strconv.FormatInt(tag.Request.User.Yob, intBase) + } + return "" +} + +// MacroGender contains definition for Gender Parameter +func (tag *BidderMacro) MacroGender(key string) string { + if nil != tag.Request.User { + return tag.Request.User.Gender + } + return "" +} + +/********************* Extension *********************/ + +// MacroGDPRConsent contains definition for GDPRConsent Parameter +func (tag *BidderMacro) MacroGDPRConsent(key string) string { + if nil != tag.UserExt { + return tag.UserExt.Consent + } + return "" +} + +// MacroGDPR contains definition for GDPR Parameter +func (tag *BidderMacro) MacroGDPR(key string) string { + if nil != tag.RegsExt && nil != tag.RegsExt.GDPR { + return strconv.FormatInt(int64(*tag.RegsExt.GDPR), intBase) + } + return "" +} + +// MacroUSPrivacy contains definition for USPrivacy Parameter +func (tag *BidderMacro) MacroUSPrivacy(key string) string { + if nil != tag.RegsExt { + return tag.RegsExt.USPrivacy + } + return "" +} + +/********************* Additional *********************/ + +// MacroCacheBuster contains definition for CacheBuster Parameter +func (tag *BidderMacro) MacroCacheBuster(key string) string { + //change implementation + return strconv.FormatInt(time.Now().UnixNano(), intBase) +} + +// MacroKV replace the kv macro +func (tag *BidderMacro) MacroKV(key string) string { + if tag.KV == nil { + return "" + } + return mapToQuery(tag.KV) +} + +// MacroKVM replace the kvm macro +func (tag *BidderMacro) MacroKVM(key string) string { + if tag.KV == nil { + return "" + } + return getJSONString(tag.KV) +} + +/********************* Request Headers *********************/ + +// setDefaultHeaders sets following default headers based on VAST protocol version +// +// X-device-IP; end users IP address, per VAST 4.x +// X-Forwarded-For; end users IP address, prior VAST versions +// X-Device-User-Agent; End users user agent, per VAST 4.x +// User-Agent; End users user agent, prior VAST versions +// X-Device-Referer; Referer value from the original request, per VAST 4.x +// X-device-Accept-Language, Accept-language value from the original request, per VAST 4.x +func setDefaultHeaders(tag *BidderMacro) { + // openrtb2. auction.go setDeviceImplicitly + // already populates OpenRTB bid request based on http request headers + // reusing the same information to set these headers via Macro* methods + headers := http.Header{} + ip := tag.IBidderMacro.MacroIP("") + userAgent := tag.IBidderMacro.MacroUserAgent("") + referer := tag.IBidderMacro.MacroSitePage("") + language := tag.IBidderMacro.MacroDeviceLanguage("") + + // 1 - vast 1 - 3 expected, 2 - vast 4 expected + expectedVastTags := 0 + if nil != tag.Imp && nil != tag.Imp.Video && nil != tag.Imp.Video.Protocols && len(tag.Imp.Video.Protocols) > 0 { + for _, protocol := range tag.Imp.Video.Protocols { + if protocol == adcom1.CreativeVAST40 || protocol == adcom1.CreativeVAST40Wrapper { + expectedVastTags |= 1 << 1 + } + if protocol <= adcom1.CreativeVAST30Wrapper { + expectedVastTags |= 1 << 0 + } + } + } else { + // not able to detect protocols. set all headers + expectedVastTags = 3 + } + + if expectedVastTags == 1 || expectedVastTags == 3 { + // vast prior to version 3 headers + setHeaders(headers, "X-Forwarded-For", ip) + setHeaders(headers, "User-Agent", userAgent) + } + + if expectedVastTags == 2 || expectedVastTags == 3 { + // vast 4 specific headers + setHeaders(headers, "X-device-Ip", ip) + setHeaders(headers, "X-Device-User-Agent", userAgent) + setHeaders(headers, "X-Device-Referer", referer) + setHeaders(headers, "X-Device-Accept-Language", language) + } + tag.ImpReqHeaders = headers +} + +func setHeaders(headers http.Header, key, value string) { + if len(value) > 0 { + headers.Set(key, value) + } +} + +// getAllHeaders combines default and custom headers and returns common list +// It internally calls GetHeaders() method for obtaining list of custom headers +func (tag *BidderMacro) getAllHeaders() http.Header { + setDefaultHeaders(tag) + customHeaders := tag.IBidderMacro.GetHeaders() + if nil != customHeaders { + for k, v := range customHeaders { + // custom header may contains default header key with value + // in such case custom value will be prefered + if nil != v && len(v) > 0 { + tag.ImpReqHeaders.Set(k, v[0]) + for i := 1; i < len(v); i++ { + tag.ImpReqHeaders.Add(k, v[i]) + } + } + } + } + return tag.ImpReqHeaders +} diff --git a/adapters/vastbidder/bidder_macro_test.go b/adapters/vastbidder/bidder_macro_test.go new file mode 100644 index 00000000000..175653ab2d7 --- /dev/null +++ b/adapters/vastbidder/bidder_macro_test.go @@ -0,0 +1,1763 @@ +package vastbidder + +import ( + "fmt" + "net/http" + "testing" + + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/config" + "github.com/stretchr/testify/assert" +) + +// TestSetDefaultHeaders verifies SetDefaultHeaders +func TestSetDefaultHeaders(t *testing.T) { + type args struct { + req *openrtb2.BidRequest + } + type want struct { + headers http.Header + } + tests := []struct { + name string + args args + want want + }{ + { + name: "check all default headers", + args: args{req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + IP: "1.1.1.1", + UA: "user-agent", + Language: "en", + }, + Site: &openrtb2.Site{ + Page: "http://test.com/", + }, + }}, + want: want{ + headers: http.Header{ + "X-Device-Ip": []string{"1.1.1.1"}, + "X-Forwarded-For": []string{"1.1.1.1"}, + "X-Device-User-Agent": []string{"user-agent"}, + "User-Agent": []string{"user-agent"}, + "X-Device-Referer": []string{"http://test.com/"}, + "X-Device-Accept-Language": []string{"en"}, + }, + }, + }, + { + name: "nil bid request", + args: args{req: nil}, + want: want{ + headers: http.Header{}, + }, + }, + { + name: "no headers set", + args: args{req: &openrtb2.BidRequest{}}, + want: want{ + headers: http.Header{}, + }, + }, { + name: "vast 4 protocol", + args: args{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + IP: "1.1.1.1", + UA: "user-agent", + Language: "en", + }, + Site: &openrtb2.Site{ + Page: "http://test.com/", + }, + Imp: []openrtb2.Imp{ + { + Video: &openrtb2.Video{ + Protocols: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST40, + adcom1.CreativeDAAST10, + }, + }, + }, + }, + }, + }, + want: want{ + headers: http.Header{ + "X-Device-Ip": []string{"1.1.1.1"}, + "X-Device-User-Agent": []string{"user-agent"}, + "X-Device-Referer": []string{"http://test.com/"}, + "X-Device-Accept-Language": []string{"en"}, + }, + }, + }, { + name: "< vast 4", + args: args{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + IP: "1.1.1.1", + UA: "user-agent", + Language: "en", + }, + Site: &openrtb2.Site{ + Page: "http://test.com/", + }, + Imp: []openrtb2.Imp{ + { + Video: &openrtb2.Video{ + Protocols: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST20, + adcom1.CreativeDAAST10, + }, + }, + }, + }, + }, + }, + want: want{ + headers: http.Header{ + "X-Forwarded-For": []string{"1.1.1.1"}, + "User-Agent": []string{"user-agent"}, + }, + }, + }, { + name: "vast 4.0 and 4.0 wrapper", + args: args{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + IP: "1.1.1.1", + UA: "user-agent", + Language: "en", + }, + Site: &openrtb2.Site{ + Page: "http://test.com/", + }, + Imp: []openrtb2.Imp{ + { + Video: &openrtb2.Video{ + Protocols: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST40, + adcom1.CreativeVAST40Wrapper, + }, + }, + }, + }, + }, + }, + want: want{ + headers: http.Header{ + "X-Device-Ip": []string{"1.1.1.1"}, + "X-Device-User-Agent": []string{"user-agent"}, + "X-Device-Referer": []string{"http://test.com/"}, + "X-Device-Accept-Language": []string{"en"}, + }, + }, + }, + { + name: "vast 2.0 and 4.0", + args: args{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + IP: "1.1.1.1", + UA: "user-agent", + Language: "en", + }, + Site: &openrtb2.Site{ + Page: "http://test.com/", + }, + Imp: []openrtb2.Imp{ + { + Video: &openrtb2.Video{ + Protocols: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST40, + adcom1.CreativeVAST20Wrapper, + }, + }, + }, + }, + }, + }, + want: want{ + headers: http.Header{ + "X-Device-Ip": []string{"1.1.1.1"}, + "X-Forwarded-For": []string{"1.1.1.1"}, + "X-Device-User-Agent": []string{"user-agent"}, + "User-Agent": []string{"user-agent"}, + "X-Device-Referer": []string{"http://test.com/"}, + "X-Device-Accept-Language": []string{"en"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tag := new(BidderMacro) + tag.IBidderMacro = tag + tag.IsApp = false + tag.Request = tt.args.req + if nil != tt.args.req && nil != tt.args.req.Imp && len(tt.args.req.Imp) > 0 { + tag.Imp = &tt.args.req.Imp[0] + } + setDefaultHeaders(tag) + assert.Equal(t, tt.want.headers, tag.ImpReqHeaders) + }) + } +} + +// TestGetAllHeaders verifies default and custom headers are returned +func TestGetAllHeaders(t *testing.T) { + type args struct { + req *openrtb2.BidRequest + myBidder IBidderMacro + } + type want struct { + headers http.Header + } + + tests := []struct { + name string + args args + want want + }{ + { + name: "Default and custom headers check", + args: args{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + IP: "1.1.1.1", + UA: "user-agent", + Language: "en", + }, + Site: &openrtb2.Site{ + Page: "http://test.com/", + }, + }, + myBidder: newMyVastBidderMacro(map[string]string{ + "my-custom-header": "some-value", + }), + }, + want: want{ + headers: http.Header{ + "X-Device-Ip": []string{"1.1.1.1"}, + "X-Forwarded-For": []string{"1.1.1.1"}, + "X-Device-User-Agent": []string{"user-agent"}, + "User-Agent": []string{"user-agent"}, + "X-Device-Referer": []string{"http://test.com/"}, + "X-Device-Accept-Language": []string{"en"}, + "My-Custom-Header": []string{"some-value"}, + }, + }, + }, + { + name: "override default header value", + args: args{ + req: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Page: "http://test.com/", // default header value + }, + }, + myBidder: newMyVastBidderMacro(map[string]string{ + "X-Device-Referer": "my-custom-value", + }), + }, + want: want{ + headers: http.Header{ + // http://test.com/ is not expected here as value + "X-Device-Referer": []string{"my-custom-value"}, + }, + }, + }, + { + name: "no custom headers", + args: args{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + IP: "1.1.1.1", + UA: "user-agent", + Language: "en", + }, + Site: &openrtb2.Site{ + Page: "http://test.com/", + }, + }, + myBidder: newMyVastBidderMacro(nil), // nil - no custom headers + }, + want: want{ + headers: http.Header{ // expect default headers + "X-Device-Ip": []string{"1.1.1.1"}, + "X-Forwarded-For": []string{"1.1.1.1"}, + "X-Device-User-Agent": []string{"user-agent"}, + "User-Agent": []string{"user-agent"}, + "X-Device-Referer": []string{"http://test.com/"}, + "X-Device-Accept-Language": []string{"en"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tag := tt.args.myBidder + tag.(*myVastBidderMacro).Request = tt.args.req + allHeaders := tag.getAllHeaders() + assert.Equal(t, tt.want.headers, allHeaders) + }) + } +} + +type myVastBidderMacro struct { + *BidderMacro + customHeaders map[string]string +} + +func newMyVastBidderMacro(customHeaders map[string]string) IBidderMacro { + obj := &myVastBidderMacro{ + BidderMacro: &BidderMacro{}, + customHeaders: customHeaders, + } + obj.IBidderMacro = obj + return obj +} + +func (tag *myVastBidderMacro) GetHeaders() http.Header { + if nil == tag.customHeaders { + return nil + } + h := http.Header{} + for k, v := range tag.customHeaders { + h.Set(k, v) + } + return h +} + +type testBidderMacro struct { + *BidderMacro +} + +func (tag *testBidderMacro) MacroCacheBuster(key string) string { + return `cachebuster` +} + +func newTestBidderMacro() IBidderMacro { + obj := &testBidderMacro{ + BidderMacro: &BidderMacro{}, + } + obj.IBidderMacro = obj + return obj +} + +func TestBidderMacro_MacroTest(t *testing.T) { + type args struct { + tag IBidderMacro + conf *config.Adapter + bidRequest *openrtb2.BidRequest + } + tests := []struct { + name string + args args + macros map[string]string + }{ + { + name: `App:EmptyBasicRequest`, + args: args{ + tag: newTestBidderMacro(), + conf: &config.Adapter{}, + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + Video: &openrtb2.Video{}, + }, + }, + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{}, + }, + }, + }, + macros: map[string]string{ + MacroTest: ``, + MacroTimeout: ``, + MacroWhitelistSeat: ``, + MacroWhitelistLang: ``, + MacroBlockedSeat: ``, + MacroCurrency: ``, + MacroBlockedCategory: ``, + MacroBlockedAdvertiser: ``, + MacroBlockedApp: ``, + MacroFD: ``, + MacroTransactionID: ``, + MacroPaymentIDChain: ``, + MacroCoppa: ``, + MacroDisplayManager: ``, + MacroDisplayManagerVersion: ``, + MacroInterstitial: ``, + MacroTagID: ``, + MacroBidFloor: ``, + MacroBidFloorCurrency: ``, + MacroSecure: ``, + MacroPMP: ``, + MacroVideoMIMES: ``, + MacroVideoMinimumDuration: ``, + MacroVideoMaximumDuration: ``, + MacroVideoProtocols: ``, + MacroVideoPlayerWidth: ``, + MacroVideoPlayerHeight: ``, + MacroVideoStartDelay: ``, + MacroVideoPlacement: ``, + MacroVideoLinearity: ``, + MacroVideoSkip: ``, + MacroVideoSkipMinimum: ``, + MacroVideoSkipAfter: ``, + MacroVideoSequence: ``, + MacroVideoBlockedAttribute: ``, + MacroVideoMaximumExtended: ``, + MacroVideoMinimumBitRate: ``, + MacroVideoMaximumBitRate: ``, + MacroVideoBoxing: ``, + MacroVideoPlaybackMethod: ``, + MacroVideoDelivery: ``, + MacroVideoPosition: ``, + MacroVideoAPI: ``, + MacroSiteID: ``, + MacroSiteName: ``, + MacroSitePage: ``, + MacroSiteReferrer: ``, + MacroSiteSearch: ``, + MacroSiteMobile: ``, + MacroAppID: ``, + MacroAppName: ``, + MacroAppBundle: ``, + MacroAppStoreURL: ``, + MacroAppVersion: ``, + MacroAppPaid: ``, + MacroCategory: ``, + MacroDomain: ``, + MacroSectionCategory: ``, + MacroPageCategory: ``, + MacroPrivacyPolicy: ``, + MacroKeywords: ``, + MacroPubID: ``, + MacroPubName: ``, + MacroPubDomain: ``, + MacroContentID: ``, + MacroContentEpisode: ``, + MacroContentTitle: ``, + MacroContentSeries: ``, + MacroContentSeason: ``, + MacroContentArtist: ``, + MacroContentGenre: ``, + MacroContentAlbum: ``, + MacroContentISrc: ``, + MacroContentURL: ``, + MacroContentCategory: ``, + MacroContentProductionQuality: ``, + MacroContentVideoQuality: ``, + MacroContentContext: ``, + MacroContentContentRating: ``, + MacroContentUserRating: ``, + MacroContentQAGMediaRating: ``, + MacroContentKeywords: ``, + MacroContentLiveStream: ``, + MacroContentSourceRelationship: ``, + MacroContentLength: ``, + MacroContentLanguage: ``, + MacroContentEmbeddable: ``, + MacroProducerID: ``, + MacroProducerName: ``, + MacroUserAgent: ``, + MacroDNT: ``, + MacroLMT: ``, + MacroIP: ``, + MacroDeviceType: ``, + MacroMake: ``, + MacroModel: ``, + MacroDeviceOS: ``, + MacroDeviceOSVersion: ``, + MacroDeviceWidth: ``, + MacroDeviceHeight: ``, + MacroDeviceJS: ``, + MacroDeviceLanguage: ``, + MacroDeviceIFA: ``, + MacroDeviceIFAType: ``, + MacroDeviceDIDSHA1: ``, + MacroDeviceDIDMD5: ``, + MacroDeviceDPIDSHA1: ``, + MacroDeviceDPIDMD5: ``, + MacroDeviceMACSHA1: ``, + MacroDeviceMACMD5: ``, + MacroLatitude: ``, + MacroLongitude: ``, + MacroCountry: ``, + MacroRegion: ``, + MacroCity: ``, + MacroZip: ``, + MacroUTCOffset: ``, + MacroUserID: ``, + MacroYearOfBirth: ``, + MacroGender: ``, + MacroGDPRConsent: ``, + MacroGDPR: ``, + MacroUSPrivacy: ``, + MacroCacheBuster: `cachebuster`, + }, + }, + { + name: `Site:EmptyBasicRequest`, + args: args{ + tag: newTestBidderMacro(), + conf: &config.Adapter{}, + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + Video: &openrtb2.Video{}, + }, + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{}, + }, + }, + }, + macros: map[string]string{ + MacroTest: ``, + MacroTimeout: ``, + MacroWhitelistSeat: ``, + MacroWhitelistLang: ``, + MacroBlockedSeat: ``, + MacroCurrency: ``, + MacroBlockedCategory: ``, + MacroBlockedAdvertiser: ``, + MacroBlockedApp: ``, + MacroFD: ``, + MacroTransactionID: ``, + MacroPaymentIDChain: ``, + MacroCoppa: ``, + MacroDisplayManager: ``, + MacroDisplayManagerVersion: ``, + MacroInterstitial: ``, + MacroTagID: ``, + MacroBidFloor: ``, + MacroBidFloorCurrency: ``, + MacroSecure: ``, + MacroPMP: ``, + MacroVideoMIMES: ``, + MacroVideoMinimumDuration: ``, + MacroVideoMaximumDuration: ``, + MacroVideoProtocols: ``, + MacroVideoPlayerWidth: ``, + MacroVideoPlayerHeight: ``, + MacroVideoStartDelay: ``, + MacroVideoPlacement: ``, + MacroVideoLinearity: ``, + MacroVideoSkip: ``, + MacroVideoSkipMinimum: ``, + MacroVideoSkipAfter: ``, + MacroVideoSequence: ``, + MacroVideoBlockedAttribute: ``, + MacroVideoMaximumExtended: ``, + MacroVideoMinimumBitRate: ``, + MacroVideoMaximumBitRate: ``, + MacroVideoBoxing: ``, + MacroVideoPlaybackMethod: ``, + MacroVideoDelivery: ``, + MacroVideoPosition: ``, + MacroVideoAPI: ``, + MacroSiteID: ``, + MacroSiteName: ``, + MacroSitePage: ``, + MacroSiteReferrer: ``, + MacroSiteSearch: ``, + MacroSiteMobile: ``, + MacroAppID: ``, + MacroAppName: ``, + MacroAppBundle: ``, + MacroAppStoreURL: ``, + MacroAppVersion: ``, + MacroAppPaid: ``, + MacroCategory: ``, + MacroDomain: ``, + MacroSectionCategory: ``, + MacroPageCategory: ``, + MacroPrivacyPolicy: ``, + MacroKeywords: ``, + MacroPubID: ``, + MacroPubName: ``, + MacroPubDomain: ``, + MacroContentID: ``, + MacroContentEpisode: ``, + MacroContentTitle: ``, + MacroContentSeries: ``, + MacroContentSeason: ``, + MacroContentArtist: ``, + MacroContentGenre: ``, + MacroContentAlbum: ``, + MacroContentISrc: ``, + MacroContentURL: ``, + MacroContentCategory: ``, + MacroContentProductionQuality: ``, + MacroContentVideoQuality: ``, + MacroContentContext: ``, + MacroContentContentRating: ``, + MacroContentUserRating: ``, + MacroContentQAGMediaRating: ``, + MacroContentKeywords: ``, + MacroContentLiveStream: ``, + MacroContentSourceRelationship: ``, + MacroContentLength: ``, + MacroContentLanguage: ``, + MacroContentEmbeddable: ``, + MacroProducerID: ``, + MacroProducerName: ``, + MacroUserAgent: ``, + MacroDNT: ``, + MacroLMT: ``, + MacroIP: ``, + MacroDeviceType: ``, + MacroMake: ``, + MacroModel: ``, + MacroDeviceOS: ``, + MacroDeviceOSVersion: ``, + MacroDeviceWidth: ``, + MacroDeviceHeight: ``, + MacroDeviceJS: ``, + MacroDeviceLanguage: ``, + MacroDeviceIFA: ``, + MacroDeviceIFAType: ``, + MacroDeviceDIDSHA1: ``, + MacroDeviceDIDMD5: ``, + MacroDeviceDPIDSHA1: ``, + MacroDeviceDPIDMD5: ``, + MacroDeviceMACSHA1: ``, + MacroDeviceMACMD5: ``, + MacroLatitude: ``, + MacroLongitude: ``, + MacroCountry: ``, + MacroRegion: ``, + MacroCity: ``, + MacroZip: ``, + MacroUTCOffset: ``, + MacroUserID: ``, + MacroYearOfBirth: ``, + MacroGender: ``, + MacroGDPRConsent: ``, + MacroGDPR: ``, + MacroUSPrivacy: ``, + MacroCacheBuster: `cachebuster`, + }, + }, + { + name: `Site:RequestLevelMacros`, + args: args{ + tag: newTestBidderMacro(), + conf: &config.Adapter{}, + bidRequest: &openrtb2.BidRequest{ + Test: 1, + TMax: 1000, + WSeat: []string{`wseat-1`, `wseat-2`}, + WLang: []string{`wlang-1`, `wlang-2`}, + BSeat: []string{`bseat-1`, `bseat-2`}, + Cur: []string{`usd`, `inr`}, + BCat: []string{`bcat-1`, `bcat-2`}, + BAdv: []string{`badv-1`, `badv-2`}, + BApp: []string{`bapp-1`, `bapp-2`}, + Source: &openrtb2.Source{ + FD: 1, + TID: `source-tid`, + PChain: `source-pchain`, + }, + Regs: &openrtb2.Regs{ + COPPA: 1, + Ext: []byte(`{"gdpr":1,"us_privacy":"user-privacy"}`), + }, + Imp: []openrtb2.Imp{ + { + DisplayManager: `disp-mgr`, + DisplayManagerVer: `1.2`, + Instl: 1, + TagID: `tag-id`, + BidFloor: 3.0, + BidFloorCur: `usd`, + Secure: new(int8), + PMP: &openrtb2.PMP{ + PrivateAuction: 1, + Deals: []openrtb2.Deal{ + { + ID: `deal-1`, + BidFloor: 4.0, + BidFloorCur: `usd`, + AT: 1, + WSeat: []string{`wseat-11`, `wseat-12`}, + WADomain: []string{`wdomain-11`, `wdomain-12`}, + }, + { + ID: `deal-2`, + BidFloor: 5.0, + BidFloorCur: `inr`, + AT: 1, + WSeat: []string{`wseat-21`, `wseat-22`}, + WADomain: []string{`wdomain-21`, `wdomain-22`}, + }, + }, + }, + Video: &openrtb2.Video{ + MIMEs: []string{`mp4`, `flv`}, + MinDuration: 30, + MaxDuration: 60, + Protocols: []adcom1.MediaCreativeSubtype{adcom1.CreativeVAST30, adcom1.CreativeVAST40Wrapper}, + Protocol: adcom1.CreativeVAST40Wrapper, + W: 640, + H: 480, + StartDelay: new(adcom1.StartDelay), + Placement: adcom1.VideoPlacementInStream, + Linearity: adcom1.LinearityLinear, + Skip: new(int8), + SkipMin: 10, + SkipAfter: 5, + Sequence: 1, + BAttr: []adcom1.CreativeAttribute{adcom1.AttrAudioAuto, adcom1.AttrAudioUser}, + MaxExtended: 10, + MinBitRate: 360, + MaxBitRate: 1080, + BoxingAllowed: 1, + PlaybackMethod: []adcom1.PlaybackMethod{adcom1.PlaybackPageLoadSoundOn, adcom1.PlaybackClickSoundOn}, + PlaybackEnd: adcom1.PlaybackCompletion, + Delivery: []adcom1.DeliveryMethod{adcom1.DeliveryStreaming, adcom1.DeliveryDownload}, + Pos: new(adcom1.PlacementPosition), + API: []adcom1.APIFramework{adcom1.APIVPAID10, adcom1.APIVPAID20}, + }, + }, + }, + Site: &openrtb2.Site{ + ID: `site-id`, + Name: `site-name`, + Domain: `site-domain`, + Cat: []string{`site-cat1`, `site-cat2`}, + SectionCat: []string{`site-sec-cat1`, `site-sec-cat2`}, + PageCat: []string{`site-page-cat1`, `site-page-cat2`}, + Page: `site-page-url`, + Ref: `site-referer-url`, + Search: `site-search-keywords`, + Mobile: 1, + PrivacyPolicy: 2, + Keywords: `site-keywords`, + Publisher: &openrtb2.Publisher{ + ID: `site-pub-id`, + Name: `site-pub-name`, + Domain: `site-pub-domain`, + }, + Content: &openrtb2.Content{ + ID: `site-cnt-id`, + Episode: 2, + Title: `site-cnt-title`, + Series: `site-cnt-series`, + Season: `site-cnt-season`, + Artist: `site-cnt-artist`, + Genre: `site-cnt-genre`, + Album: `site-cnt-album`, + ISRC: `site-cnt-isrc`, + URL: `site-cnt-url`, + Cat: []string{`site-cnt-cat1`, `site-cnt-cat2`}, + ProdQ: new(adcom1.ProductionQuality), + VideoQuality: new(adcom1.ProductionQuality), + Context: adcom1.ContentVideo, + ContentRating: `1.2`, + UserRating: `2.2`, + QAGMediaRating: adcom1.MediaRatingAll, + Keywords: `site-cnt-keywords`, + LiveStream: 1, + SourceRelationship: 1, + Len: 100, + Language: `english`, + Embeddable: 1, + Producer: &openrtb2.Producer{ + ID: `site-cnt-prod-id`, + Name: `site-cnt-prod-name`, + }, + }, + }, + Device: &openrtb2.Device{ + UA: `user-agent`, + DNT: new(int8), + Lmt: new(int8), + IP: `ipv4`, + IPv6: `ipv6`, + DeviceType: adcom1.DeviceTV, + Make: `device-make`, + Model: `device-model`, + OS: `os`, + OSV: `os-version`, + H: 1024, + W: 2048, + JS: 1, + Language: `device-lang`, + ConnectionType: new(adcom1.ConnectionType), + IFA: `ifa`, + DIDSHA1: `didsha1`, + DIDMD5: `didmd5`, + DPIDSHA1: `dpidsha1`, + DPIDMD5: `dpidmd5`, + MACSHA1: `macsha1`, + MACMD5: `macmd5`, + Geo: &openrtb2.Geo{ + Lat: 1.1, + Lon: 2.2, + Country: `country`, + Region: `region`, + City: `city`, + ZIP: `zip`, + UTCOffset: 1000, + }, + Ext: []byte(`{"ifa_type":"idfa"}`), + }, + User: &openrtb2.User{ + ID: `user-id`, + Yob: 1990, + Gender: `M`, + Ext: []byte(`{"consent":"user-gdpr-consent"}`), + }, + }, + }, + macros: map[string]string{ + MacroTest: `1`, + MacroTimeout: `1000`, + MacroWhitelistSeat: `wseat-1,wseat-2`, + MacroWhitelistLang: `wlang-1,wlang-2`, + MacroBlockedSeat: `bseat-1,bseat-2`, + MacroCurrency: `usd,inr`, + MacroBlockedCategory: `bcat-1,bcat-2`, + MacroBlockedAdvertiser: `badv-1,badv-2`, + MacroBlockedApp: `bapp-1,bapp-2`, + MacroFD: `1`, + MacroTransactionID: `source-tid`, + MacroPaymentIDChain: `source-pchain`, + MacroCoppa: `1`, + MacroDisplayManager: `disp-mgr`, + MacroDisplayManagerVersion: `1.2`, + MacroInterstitial: `1`, + MacroTagID: `tag-id`, + MacroBidFloor: `3`, + MacroBidFloorCurrency: `usd`, + MacroSecure: `0`, + MacroPMP: `{"private_auction":1,"deals":[{"id":"deal-1","bidfloor":4,"bidfloorcur":"usd","at":1,"wseat":["wseat-11","wseat-12"],"wadomain":["wdomain-11","wdomain-12"]},{"id":"deal-2","bidfloor":5,"bidfloorcur":"inr","at":1,"wseat":["wseat-21","wseat-22"],"wadomain":["wdomain-21","wdomain-22"]}]}`, + MacroVideoMIMES: `mp4,flv`, + MacroVideoMinimumDuration: `30`, + MacroVideoMaximumDuration: `60`, + MacroVideoProtocols: `3,8`, + MacroVideoPlayerWidth: `640`, + MacroVideoPlayerHeight: `480`, + MacroVideoStartDelay: `0`, + MacroVideoPlacement: `1`, + MacroVideoLinearity: `1`, + MacroVideoSkip: `0`, + MacroVideoSkipMinimum: `10`, + MacroVideoSkipAfter: `5`, + MacroVideoSequence: `1`, + MacroVideoBlockedAttribute: `1,2`, + MacroVideoMaximumExtended: `10`, + MacroVideoMinimumBitRate: `360`, + MacroVideoMaximumBitRate: `1080`, + MacroVideoBoxing: `1`, + MacroVideoPlaybackMethod: `1,3`, + MacroVideoDelivery: `1,3`, + MacroVideoPosition: `0`, + MacroVideoAPI: `1,2`, + MacroSiteID: `site-id`, + MacroSiteName: `site-name`, + MacroSitePage: `site-page-url`, + MacroSiteReferrer: `site-referer-url`, + MacroSiteSearch: `site-search-keywords`, + MacroSiteMobile: `1`, + MacroAppID: ``, + MacroAppName: ``, + MacroAppBundle: ``, + MacroAppStoreURL: ``, + MacroAppVersion: ``, + MacroAppPaid: ``, + MacroCategory: `site-cat1,site-cat2`, + MacroDomain: `site-domain`, + MacroSectionCategory: `site-sec-cat1,site-sec-cat2`, + MacroPageCategory: `site-page-cat1,site-page-cat2`, + MacroPrivacyPolicy: `2`, + MacroKeywords: `site-keywords`, + MacroPubID: `site-pub-id`, + MacroPubName: `site-pub-name`, + MacroPubDomain: `site-pub-domain`, + MacroContentID: `site-cnt-id`, + MacroContentEpisode: `2`, + MacroContentTitle: `site-cnt-title`, + MacroContentSeries: `site-cnt-series`, + MacroContentSeason: `site-cnt-season`, + MacroContentArtist: `site-cnt-artist`, + MacroContentGenre: `site-cnt-genre`, + MacroContentAlbum: `site-cnt-album`, + MacroContentISrc: `site-cnt-isrc`, + MacroContentURL: `site-cnt-url`, + MacroContentCategory: `site-cnt-cat1,site-cnt-cat2`, + MacroContentProductionQuality: `0`, + MacroContentVideoQuality: `0`, + MacroContentContext: `1`, + MacroContentContentRating: `1.2`, + MacroContentUserRating: `2.2`, + MacroContentQAGMediaRating: `1`, + MacroContentKeywords: `site-cnt-keywords`, + MacroContentLiveStream: `1`, + MacroContentSourceRelationship: `1`, + MacroContentLength: `100`, + MacroContentLanguage: `english`, + MacroContentEmbeddable: `1`, + MacroProducerID: `site-cnt-prod-id`, + MacroProducerName: `site-cnt-prod-name`, + MacroUserAgent: `user-agent`, + MacroDNT: `0`, + MacroLMT: `0`, + MacroIP: `ipv4`, + MacroDeviceType: `3`, + MacroMake: `device-make`, + MacroModel: `device-model`, + MacroDeviceOS: `os`, + MacroDeviceOSVersion: `os-version`, + MacroDeviceWidth: `2048`, + MacroDeviceHeight: `1024`, + MacroDeviceJS: `1`, + MacroDeviceLanguage: `device-lang`, + MacroDeviceIFA: `ifa`, + MacroDeviceIFAType: `idfa`, + MacroDeviceDIDSHA1: `didsha1`, + MacroDeviceDIDMD5: `didmd5`, + MacroDeviceDPIDSHA1: `dpidsha1`, + MacroDeviceDPIDMD5: `dpidmd5`, + MacroDeviceMACSHA1: `macsha1`, + MacroDeviceMACMD5: `macmd5`, + MacroLatitude: `1.1`, + MacroLongitude: `2.2`, + MacroCountry: `country`, + MacroRegion: `region`, + MacroCity: `city`, + MacroZip: `zip`, + MacroUTCOffset: `1000`, + MacroUserID: `user-id`, + MacroYearOfBirth: `1990`, + MacroGender: `M`, + MacroGDPRConsent: `user-gdpr-consent`, + MacroGDPR: `1`, + MacroUSPrivacy: `user-privacy`, + MacroCacheBuster: `cachebuster`, + }, + }, + { + name: `App:RequestLevelMacros`, + args: args{ + tag: newTestBidderMacro(), + conf: &config.Adapter{}, + bidRequest: &openrtb2.BidRequest{ + Test: 1, + TMax: 1000, + WSeat: []string{`wseat-1`, `wseat-2`}, + WLang: []string{`wlang-1`, `wlang-2`}, + BSeat: []string{`bseat-1`, `bseat-2`}, + Cur: []string{`usd`, `inr`}, + BCat: []string{`bcat-1`, `bcat-2`}, + BAdv: []string{`badv-1`, `badv-2`}, + BApp: []string{`bapp-1`, `bapp-2`}, + Source: &openrtb2.Source{ + FD: 1, + TID: `source-tid`, + PChain: `source-pchain`, + }, + Regs: &openrtb2.Regs{ + COPPA: 1, + Ext: []byte(`{"gdpr":1,"us_privacy":"user-privacy"}`), + }, + Imp: []openrtb2.Imp{ + { + DisplayManager: `disp-mgr`, + DisplayManagerVer: `1.2`, + Instl: 1, + TagID: `tag-id`, + BidFloor: 3.0, + BidFloorCur: `usd`, + Secure: new(int8), + PMP: &openrtb2.PMP{ + PrivateAuction: 1, + Deals: []openrtb2.Deal{ + { + ID: `deal-1`, + BidFloor: 4.0, + BidFloorCur: `usd`, + AT: 1, + WSeat: []string{`wseat-11`, `wseat-12`}, + WADomain: []string{`wdomain-11`, `wdomain-12`}, + }, + { + ID: `deal-2`, + BidFloor: 5.0, + BidFloorCur: `inr`, + AT: 1, + WSeat: []string{`wseat-21`, `wseat-22`}, + WADomain: []string{`wdomain-21`, `wdomain-22`}, + }, + }, + }, + Video: &openrtb2.Video{ + MIMEs: []string{`mp4`, `flv`}, + MinDuration: 30, + MaxDuration: 60, + Protocols: []adcom1.MediaCreativeSubtype{adcom1.CreativeVAST30, adcom1.CreativeVAST40Wrapper}, + Protocol: adcom1.CreativeVAST40Wrapper, + W: 640, + H: 480, + StartDelay: new(adcom1.StartDelay), + Placement: adcom1.VideoPlacementInStream, + Linearity: adcom1.LinearityLinear, + Skip: new(int8), + SkipMin: 10, + SkipAfter: 5, + Sequence: 1, + BAttr: []adcom1.CreativeAttribute{adcom1.AttrAudioAuto, adcom1.AttrAudioUser}, + MaxExtended: 10, + MinBitRate: 360, + MaxBitRate: 1080, + BoxingAllowed: 1, + PlaybackMethod: []adcom1.PlaybackMethod{adcom1.PlaybackPageLoadSoundOn, adcom1.PlaybackClickSoundOn}, + PlaybackEnd: adcom1.PlaybackCompletion, + Delivery: []adcom1.DeliveryMethod{adcom1.DeliveryStreaming, adcom1.DeliveryDownload}, + Pos: new(adcom1.PlacementPosition), + API: []adcom1.APIFramework{adcom1.APIVPAID10, adcom1.APIVPAID20}, + }, + }, + }, + App: &openrtb2.App{ + ID: `app-id`, + Bundle: `app-bundle`, + StoreURL: `app-store-url`, + Ver: `app-version`, + Paid: 1, + Name: `app-name`, + Domain: `app-domain`, + Cat: []string{`app-cat1`, `app-cat2`}, + SectionCat: []string{`app-sec-cat1`, `app-sec-cat2`}, + PageCat: []string{`app-page-cat1`, `app-page-cat2`}, + PrivacyPolicy: 2, + Keywords: `app-keywords`, + Publisher: &openrtb2.Publisher{ + ID: `app-pub-id`, + Name: `app-pub-name`, + Domain: `app-pub-domain`, + }, + Content: &openrtb2.Content{ + ID: `app-cnt-id`, + Episode: 2, + Title: `app-cnt-title`, + Series: `app-cnt-series`, + Season: `app-cnt-season`, + Artist: `app-cnt-artist`, + Genre: `app-cnt-genre`, + Album: `app-cnt-album`, + ISRC: `app-cnt-isrc`, + URL: `app-cnt-url`, + Cat: []string{`app-cnt-cat1`, `app-cnt-cat2`}, + ProdQ: new(adcom1.ProductionQuality), + VideoQuality: new(adcom1.ProductionQuality), + Context: adcom1.ContentVideo, + ContentRating: `1.2`, + UserRating: `2.2`, + QAGMediaRating: adcom1.MediaRatingAll, + Keywords: `app-cnt-keywords`, + LiveStream: 1, + SourceRelationship: 1, + Len: 100, + Language: `english`, + Embeddable: 1, + Producer: &openrtb2.Producer{ + ID: `app-cnt-prod-id`, + Name: `app-cnt-prod-name`, + }, + }, + }, + Device: &openrtb2.Device{ + UA: `user-agent`, + DNT: new(int8), + Lmt: new(int8), + IPv6: `ipv6`, + DeviceType: adcom1.DeviceTV, + Make: `device-make`, + Model: `device-model`, + OS: `os`, + OSV: `os-version`, + H: 1024, + W: 2048, + JS: 1, + Language: `device-lang`, + ConnectionType: new(adcom1.ConnectionType), + IFA: `ifa`, + DIDSHA1: `didsha1`, + DIDMD5: `didmd5`, + DPIDSHA1: `dpidsha1`, + DPIDMD5: `dpidmd5`, + MACSHA1: `macsha1`, + MACMD5: `macmd5`, + Geo: &openrtb2.Geo{ + Lat: 1.1, + Lon: 2.2, + Country: `country`, + Region: `region`, + City: `city`, + ZIP: `zip`, + UTCOffset: 1000, + }, + Ext: []byte(`{"ifa_type":"idfa"}`), + }, + User: &openrtb2.User{ + ID: `user-id`, + Yob: 1990, + Gender: `M`, + Ext: []byte(`{"consent":"user-gdpr-consent"}`), + }, + }, + }, + macros: map[string]string{ + MacroTest: `1`, + MacroTimeout: `1000`, + MacroWhitelistSeat: `wseat-1,wseat-2`, + MacroWhitelistLang: `wlang-1,wlang-2`, + MacroBlockedSeat: `bseat-1,bseat-2`, + MacroCurrency: `usd,inr`, + MacroBlockedCategory: `bcat-1,bcat-2`, + MacroBlockedAdvertiser: `badv-1,badv-2`, + MacroBlockedApp: `bapp-1,bapp-2`, + MacroFD: `1`, + MacroTransactionID: `source-tid`, + MacroPaymentIDChain: `source-pchain`, + MacroCoppa: `1`, + MacroDisplayManager: `disp-mgr`, + MacroDisplayManagerVersion: `1.2`, + MacroInterstitial: `1`, + MacroTagID: `tag-id`, + MacroBidFloor: `3`, + MacroBidFloorCurrency: `usd`, + MacroSecure: `0`, + MacroPMP: `{"private_auction":1,"deals":[{"id":"deal-1","bidfloor":4,"bidfloorcur":"usd","at":1,"wseat":["wseat-11","wseat-12"],"wadomain":["wdomain-11","wdomain-12"]},{"id":"deal-2","bidfloor":5,"bidfloorcur":"inr","at":1,"wseat":["wseat-21","wseat-22"],"wadomain":["wdomain-21","wdomain-22"]}]}`, + MacroVideoMIMES: `mp4,flv`, + MacroVideoMinimumDuration: `30`, + MacroVideoMaximumDuration: `60`, + MacroVideoProtocols: `3,8`, + MacroVideoPlayerWidth: `640`, + MacroVideoPlayerHeight: `480`, + MacroVideoStartDelay: `0`, + MacroVideoPlacement: `1`, + MacroVideoLinearity: `1`, + MacroVideoSkip: `0`, + MacroVideoSkipMinimum: `10`, + MacroVideoSkipAfter: `5`, + MacroVideoSequence: `1`, + MacroVideoBlockedAttribute: `1,2`, + MacroVideoMaximumExtended: `10`, + MacroVideoMinimumBitRate: `360`, + MacroVideoMaximumBitRate: `1080`, + MacroVideoBoxing: `1`, + MacroVideoPlaybackMethod: `1,3`, + MacroVideoDelivery: `1,3`, + MacroVideoPosition: `0`, + MacroVideoAPI: `1,2`, + MacroSiteID: ``, + MacroSiteName: ``, + MacroSitePage: ``, + MacroSiteReferrer: ``, + MacroSiteSearch: ``, + MacroSiteMobile: ``, + MacroAppID: `app-id`, + MacroAppName: `app-name`, + MacroAppBundle: `app-bundle`, + MacroAppStoreURL: `app-store-url`, + MacroAppVersion: `app-version`, + MacroAppPaid: `1`, + MacroCategory: `app-cat1,app-cat2`, + MacroDomain: `app-domain`, + MacroSectionCategory: `app-sec-cat1,app-sec-cat2`, + MacroPageCategory: `app-page-cat1,app-page-cat2`, + MacroPrivacyPolicy: `2`, + MacroKeywords: `app-keywords`, + MacroPubID: `app-pub-id`, + MacroPubName: `app-pub-name`, + MacroPubDomain: `app-pub-domain`, + MacroContentID: `app-cnt-id`, + MacroContentEpisode: `2`, + MacroContentTitle: `app-cnt-title`, + MacroContentSeries: `app-cnt-series`, + MacroContentSeason: `app-cnt-season`, + MacroContentArtist: `app-cnt-artist`, + MacroContentGenre: `app-cnt-genre`, + MacroContentAlbum: `app-cnt-album`, + MacroContentISrc: `app-cnt-isrc`, + MacroContentURL: `app-cnt-url`, + MacroContentCategory: `app-cnt-cat1,app-cnt-cat2`, + MacroContentProductionQuality: `0`, + MacroContentVideoQuality: `0`, + MacroContentContext: `1`, + MacroContentContentRating: `1.2`, + MacroContentUserRating: `2.2`, + MacroContentQAGMediaRating: `1`, + MacroContentKeywords: `app-cnt-keywords`, + MacroContentLiveStream: `1`, + MacroContentSourceRelationship: `1`, + MacroContentLength: `100`, + MacroContentLanguage: `english`, + MacroContentEmbeddable: `1`, + MacroProducerID: `app-cnt-prod-id`, + MacroProducerName: `app-cnt-prod-name`, + MacroUserAgent: `user-agent`, + MacroDNT: `0`, + MacroLMT: `0`, + MacroIP: `ipv6`, + MacroDeviceType: `3`, + MacroMake: `device-make`, + MacroModel: `device-model`, + MacroDeviceOS: `os`, + MacroDeviceOSVersion: `os-version`, + MacroDeviceWidth: `2048`, + MacroDeviceHeight: `1024`, + MacroDeviceJS: `1`, + MacroDeviceLanguage: `device-lang`, + MacroDeviceIFA: `ifa`, + MacroDeviceIFAType: `idfa`, + MacroDeviceDIDSHA1: `didsha1`, + MacroDeviceDIDMD5: `didmd5`, + MacroDeviceDPIDSHA1: `dpidsha1`, + MacroDeviceDPIDMD5: `dpidmd5`, + MacroDeviceMACSHA1: `macsha1`, + MacroDeviceMACMD5: `macmd5`, + MacroLatitude: `1.1`, + MacroLongitude: `2.2`, + MacroCountry: `country`, + MacroRegion: `region`, + MacroCity: `city`, + MacroZip: `zip`, + MacroUTCOffset: `1000`, + MacroUserID: `user-id`, + MacroYearOfBirth: `1990`, + MacroGender: `M`, + MacroGDPRConsent: `user-gdpr-consent`, + MacroGDPR: `1`, + MacroUSPrivacy: `user-privacy`, + MacroCacheBuster: `cachebuster`, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + macroMappings := GetDefaultMapper() + + tag := tt.args.tag + tag.InitBidRequest(tt.args.bidRequest) + tag.SetAdapterConfig(tt.args.conf) + tag.LoadImpression(&tt.args.bidRequest.Imp[0]) + + for key, result := range tt.macros { + cb, ok := macroMappings[key] + if !ok { + assert.NotEmpty(t, result) + } else { + actual := cb.callback(tag, key) + assert.Equal(t, result, actual, fmt.Sprintf("MacroFunction: %v", key)) + } + } + }) + } +} + +func TestBidderGetValue(t *testing.T) { + type fields struct { + KV map[string]interface{} + } + type args struct { + key string + } + tests := []struct { + name string + fields fields + args args + want string + isKeyFound bool // if key has the prefix kv/kvm then it should return thr isKeyFound true + }{ + { + name: "valid_Key", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "age": 22, + }}, + args: args{key: "kv.name"}, + want: "test", + isKeyFound: true, + }, + { + name: "invalid_Key", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "age": 22, + }}, + args: args{key: "kv.anykey"}, + want: "", + isKeyFound: true, + }, + { + name: "empty_kv_map", + fields: fields{KV: nil}, + args: args{key: "kv.anykey"}, + want: "", + isKeyFound: true, + }, + { + name: "kv_map_with_no_key_val_pair", + fields: fields{KV: map[string]interface{}{}}, + args: args{key: "kv.anykey"}, + want: "", + isKeyFound: true, + }, + { + name: "key_with_value_as_url", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + "url": "http://example.com?k1=v1&k2=v2", + }, + }}, + args: args{key: "kvm.country.url"}, + want: "http://example.com?k1=v1&k2=v2", + isKeyFound: true, + }, + { + name: "kvm_prefix_key_with_value_as_nested_map", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + "url": "http//example.com?k1=v1&k2=v2", + "metadata": map[string]interface{}{ + "k1": "v1", + "k2": "v2", + }, + }, + }}, + args: args{key: "kvm.country"}, + want: "{\"metadata\":{\"k1\":\"v1\",\"k2\":\"v2\"},\"pincode\":411041,\"state\":\"MH\",\"url\":\"http//example.com?k1=v1&k2=v2\"}", + isKeyFound: true, + }, + { + name: "kv_prefix_key_with_value_as_nested_map", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + "url": "http://example.com?k1=v1&k2=v2", + "metadata": map[string]interface{}{ + "k1": "v1", + "k2": "v2", + }, + }, + }}, + args: args{key: "kv.country"}, + want: "metadata=k1%3Dv1%26k2%3Dv2&pincode=411041&state=MH&url=http%3A%2F%2Fexample.com%3Fk1%3Dv1%26k2%3Dv2", + isKeyFound: true, + }, + { + name: "key_without_kv_kvm_prefix", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + "url": "http//example.com?k1=v1&k2=v2", + "metadata": map[string]interface{}{ + "k1": "v1", + "k2": "v2", + }, + }, + }}, + args: args{key: "someprefix.kv"}, + want: "", + isKeyFound: false, // hence this key is not starting with kv/kvm prefix we return isKeyFound as false + }, + { + name: "multi-level_key", + fields: fields{KV: map[string]interface{}{ + "k1": map[string]interface{}{ + "k2": map[string]interface{}{ + "k3": map[string]interface{}{ + "k4": map[string]interface{}{ + "name": "test", + }, + }, + }, + }, + }}, + args: args{key: "kv.k1.k2.k3.k4.name"}, + want: "test", + isKeyFound: true, + }, + { + name: "key_not_matched", + fields: fields{KV: map[string]interface{}{ + "k1": map[string]interface{}{ + "k2": map[string]interface{}{ + "k3": map[string]interface{}{ + "k4": map[string]interface{}{ + "name": "test", + }, + }, + }, + }, + }}, + args: args{key: "kv.k1.k2.k3.name"}, + want: "", + isKeyFound: true, + }, + { + name: "key_wihtout_any_prefix", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "age": 22, + }}, + args: args{key: "kv"}, + want: "", + isKeyFound: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tag := &BidderMacro{ + KV: tt.fields.KV, + } + value, isKeyFound := tag.GetValue(tt.args.key) + assert.Equal(t, tt.want, value, tt.name) + assert.Equal(t, tt.isKeyFound, isKeyFound) + }) + } +} + +func TestBidderMacroKV(t *testing.T) { + type fields struct { + KV map[string]interface{} + } + type args struct { + key string + } + tests := []struct { + name string + fields fields + args args + want string + }{ + { + name: "valid_test", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "age": "22", + }}, + args: args{key: "kv"}, + want: "age=22&name=test", + }, + { + name: "valid_test_with_url", + fields: fields{KV: map[string]interface{}{ + "age": "22", + "url": "http://example.com?k1=v1&k2=v2", + }}, + args: args{key: "kv"}, + want: "age=22&url=http%3A%2F%2Fexample.com%3Fk1%3Dv1%26k2%3Dv2", + }, + { + name: "valid_test_with_encoded_url", + fields: fields{KV: map[string]interface{}{ + "age": "22", + "url": "http%3A%2F%2Fexample.com%3Fk1%3Dv1%26k2%3Dv2", + }}, + args: args{key: "kv"}, + want: "age=22&url=http%3A%2F%2Fexample.com%3Fk1%3Dv1%26k2%3Dv2", + }, + { + name: "empty_KV_map", + fields: fields{KV: nil}, + args: args{key: "kv"}, + want: "", + }, + { + name: "kv_map_with_no_key_val_pair", + fields: fields{KV: map[string]interface{}{}}, + args: args{key: "kv"}, + want: "", + }, + { + name: "key_with_value_as_map", + fields: fields{KV: map[string]interface{}{ + "age": 22, + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + }, + }}, + args: args{key: "kv"}, + want: "age=22&country=pincode%3D411041%26state%3DMH", + }, + { + name: "key_with_value_as_nested_map", + fields: fields{KV: map[string]interface{}{ + "age": 22, + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + "metadata": map[string]interface{}{ + "k1": 223, + "k2": "v2", + }, + }, + }}, + args: args{key: "kv"}, + want: "age=22&country=metadata%3Dk1%253D223%2526k2%253Dv2%26pincode%3D411041%26state%3DMH", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tag := &BidderMacro{ + KV: tt.fields.KV, + } + got := tag.MacroKV(tt.args.key) + + assert.Equal(t, tt.want, got, tt.name) + }) + } +} + +func TestBidderMacroKVM(t *testing.T) { + type fields struct { + KV map[string]interface{} + } + type args struct { + key string + } + tests := []struct { + name string + fields fields + args args + want string + }{ + { + name: "valid_test", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "age": "22", + }}, + args: args{key: "kvm"}, + want: "{\"age\":\"22\",\"name\":\"test\"}", + }, + { + name: "empty_kv_map", + fields: fields{KV: nil}, + args: args{key: "kvm"}, + want: "", + }, + { + name: "value_as_int_data_type", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "age": 22, + }}, + args: args{key: "kvm"}, + want: "{\"age\":22,\"name\":\"test\"}", + }, + { + name: "kv_map_with_no_key_val_pair", + fields: fields{KV: map[string]interface{}{}}, + args: args{key: "kvm"}, + want: "{}", + }, + { + name: "marshal_error", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "age": make(chan int), + }}, + args: args{key: "kvm"}, + want: "", + }, + { + name: "test_with_url", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "url": "http://example.com?k1=v1&k2=v2", + }}, + args: args{key: "kvm"}, + want: "{\"name\":\"test\",\"url\":\"http://example.com?k1=v1&k2=v2\"}", + }, + { + name: "key_with_value_as_nested_map", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "age": 22, + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + "metadata": map[string]interface{}{ + "k1": "v1", + "k2": "v2", + }, + }, + }}, + args: args{key: "kvm"}, + want: "{\"age\":22,\"country\":{\"metadata\":{\"k1\":\"v1\",\"k2\":\"v2\"},\"pincode\":411041,\"state\":\"MH\"},\"name\":\"test\"}", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tag := &BidderMacro{ + KV: tt.fields.KV, + } + got := tag.MacroKVM(tt.args.key) + assert.Equal(t, tt.want, got, tt.name) + }) + } +} + +func TestMacroSchain(t *testing.T) { + + type fields struct { + Request *openrtb2.BidRequest + } + type args struct { + key string + } + tests := []struct { + name string + fields fields + args args + want string + }{ + { + name: "source_object_with_both_source.schain_and_source.ext.schain", + fields: fields{&openrtb2.BidRequest{Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{}, + Ext: []byte(`{ + "schain":{ + "complete":1, + "nodes":[ + { + "asi":"exchange1.com", + "sid":"1234&abcd", + "hp":1, + "name":"publisher name" + } + ], + "ver":"1.0" + } + }`), + }}}, + args: args{key: "schain"}, + want: "", // here we have given priority to source.schain object hence source.schain is not nil it return empty string + }, + { + name: "nil_source.schain_object", + fields: fields{&openrtb2.BidRequest{ + Source: &openrtb2.Source{ + SChain: nil, + Ext: []byte(`{ + "schain":{ + "complete":0, + "nodes":[ + { + "asi":"exchange2.com", + "sid":"abcd", + "hp":1 + } + ], + "ver":"1.0" + } + }`), + }, + }}, + args: args{key: "schain"}, + want: "1.0,0!exchange2.com,abcd,1,,,", + }, + { + name: "missing_schain_object", + fields: fields{&openrtb2.BidRequest{Source: &openrtb2.Source{ + Ext: []byte(`{ + "somechain":{ + "complete":1, + "nodes":[ + { + "asi":"exchange1.com", + "sid":"1234&abcd", + "hp":1, + "ext":{"k1":"v1"} + } + ], + "ver":"1.0" + } + }`), + }}}, + args: args{key: "schain"}, + want: "", + }, + { + name: "missing_both_source.schain_and_source.ext", + fields: fields{&openrtb2.BidRequest{Source: nil}}, + args: args{key: "schain"}, + want: "", + }, + { + name: "source.schain_is_present", + fields: fields{&openrtb2.BidRequest{Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "asi", + SID: "sid", + RID: "rid", + Name: "name", + Domain: "domain", + HP: openrtb2.Int8Ptr(1), + }, + }}, + }}}, + args: args{key: "schain"}, + want: "1.0,1!asi,sid,1,rid,name,domain", + }, + { + name: "unmarshaling_error", + fields: fields{&openrtb2.BidRequest{Source: &openrtb2.Source{ + Ext: []byte(`{ + "schain":{ + "complete":"1", + "nodes":[ + { + "asi":"exchange1.com", + "sid":"1234&abcd", + "rid":"bid-request-1", + "name":"publisher%20name", + "domain":"publisher.com", + "hp":1 + } + ], + "ver":"1.0" + } + }`), + }}}, + args: args{key: "schain"}, + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tag := &BidderMacro{ + Request: tt.fields.Request, + } + got := tag.MacroSchain(tt.args.key) + assert.Equal(t, got, tt.want, tt.name) + }) + } +} diff --git a/adapters/vastbidder/constant.go b/adapters/vastbidder/constant.go new file mode 100644 index 00000000000..ad84f225786 --- /dev/null +++ b/adapters/vastbidder/constant.go @@ -0,0 +1,177 @@ +package vastbidder + +const ( + intBase = 10 + comma = `,` +) + +// List of Tag Bidder Macros +const ( + //Request + MacroTest = `test` + MacroTimeout = `timeout` + MacroWhitelistSeat = `wseat` + MacroWhitelistLang = `wlang` + MacroBlockedSeat = `bseat` + MacroCurrency = `cur` + MacroBlockedCategory = `bcat` + MacroBlockedAdvertiser = `badv` + MacroBlockedApp = `bapp` + + //Source + MacroFD = `fd` + MacroTransactionID = `tid` + MacroPaymentIDChain = `pchain` + MacroSchain = `schain` + + //Regs + MacroCoppa = `coppa` + + //Impression + MacroDisplayManager = `displaymanager` + MacroDisplayManagerVersion = `displaymanagerver` + MacroInterstitial = `instl` + MacroTagID = `tagid` + MacroBidFloor = `bidfloor` + MacroBidFloorCurrency = `bidfloorcur` + MacroSecure = `secure` + MacroPMP = `pmp` + + //Video + MacroVideoMIMES = `mimes` + MacroVideoMinimumDuration = `minduration` + MacroVideoMaximumDuration = `maxduration` + MacroVideoProtocols = `protocols` + MacroVideoPlayerWidth = `playerwidth` + MacroVideoPlayerHeight = `playerheight` + MacroVideoStartDelay = `startdelay` + MacroVideoPlacement = `placement` + MacroVideoLinearity = `linearity` + MacroVideoSkip = `skip` + MacroVideoSkipMinimum = `skipmin` + MacroVideoSkipAfter = `skipafter` + MacroVideoSequence = `sequence` + MacroVideoBlockedAttribute = `battr` + MacroVideoMaximumExtended = `maxextended` + MacroVideoMinimumBitRate = `minbitrate` + MacroVideoMaximumBitRate = `maxbitrate` + MacroVideoBoxing = `boxingallowed` + MacroVideoPlaybackMethod = `playbackmethod` + MacroVideoDelivery = `delivery` + MacroVideoPosition = `position` + MacroVideoAPI = `api` + + //Site + MacroSiteID = `siteid` + MacroSiteName = `sitename` + MacroSitePage = `page` + MacroSiteReferrer = `ref` + MacroSiteSearch = `search` + MacroSiteMobile = `mobile` + + //App + MacroAppID = `appid` + MacroAppName = `appname` + MacroAppBundle = `bundle` + MacroAppStoreURL = `storeurl` + MacroAppVersion = `appver` + MacroAppPaid = `paid` + + //SiteAppCommon + MacroCategory = `cat` + MacroDomain = `domain` + MacroSectionCategory = `sectioncat` + MacroPageCategory = `pagecat` + MacroPrivacyPolicy = `privacypolicy` + MacroKeywords = `keywords` + + //Publisher + MacroPubID = `pubid` + MacroPubName = `pubname` + MacroPubDomain = `pubdomain` + + //Content + MacroContentID = `contentid` + MacroContentEpisode = `episode` + MacroContentTitle = `title` + MacroContentSeries = `series` + MacroContentSeason = `season` + MacroContentArtist = `artist` + MacroContentGenre = `genre` + MacroContentAlbum = `album` + MacroContentISrc = `isrc` + MacroContentURL = `contenturl` + MacroContentCategory = `contentcat` + MacroContentProductionQuality = `contentprodq` + MacroContentVideoQuality = `contentvideoquality` + MacroContentContext = `context` + MacroContentContentRating = `contentrating` + MacroContentUserRating = `userrating` + MacroContentQAGMediaRating = `qagmediarating` + MacroContentKeywords = `contentkeywords` + MacroContentLiveStream = `livestream` + MacroContentSourceRelationship = `sourcerelationship` + MacroContentLength = `contentlen` + MacroContentLanguage = `contentlanguage` + MacroContentEmbeddable = `contentembeddable` + + //Producer + MacroProducerID = `prodid` + MacroProducerName = `prodname` + + //Device + MacroUserAgent = `useragent` + MacroDNT = `dnt` + MacroLMT = `lmt` + MacroIP = `ip` + MacroDeviceType = `devicetype` + MacroMake = `make` + MacroModel = `model` + MacroDeviceOS = `os` + MacroDeviceOSVersion = `osv` + MacroDeviceWidth = `devicewidth` + MacroDeviceHeight = `deviceheight` + MacroDeviceJS = `js` + MacroDeviceLanguage = `lang` + MacroDeviceIFA = `ifa` + MacroDeviceIFAType = `ifa_type` + MacroDeviceDIDSHA1 = `didsha1` + MacroDeviceDIDMD5 = `didmd5` + MacroDeviceDPIDSHA1 = `dpidsha1` + MacroDeviceDPIDMD5 = `dpidmd5` + MacroDeviceMACSHA1 = `macsha1` + MacroDeviceMACMD5 = `macmd5` + + //Geo + MacroLatitude = `lat` + MacroLongitude = `lon` + MacroCountry = `country` + MacroRegion = `region` + MacroCity = `city` + MacroZip = `zip` + MacroUTCOffset = `utcoffset` + + //User + MacroUserID = `uid` + MacroYearOfBirth = `yob` + MacroGender = `gender` + + //Extension + MacroGDPRConsent = `consent` + MacroGDPR = `gdpr` + MacroUSPrivacy = `usprivacy` + + //Additional + MacroCacheBuster = `cachebuster` + + //KeyVal + MacroKV = `kv` + MacroKVM = `kvm` +) + +const ( + prebid = "prebid" + keyval = "keyval" +) + +var ParamKeys = []string{"param1", "param2", "param3", "param4", "param5"} diff --git a/adapters/vastbidder/ibidder_macro.go b/adapters/vastbidder/ibidder_macro.go new file mode 100644 index 00000000000..c1b95eece2d --- /dev/null +++ b/adapters/vastbidder/ibidder_macro.go @@ -0,0 +1,201 @@ +package vastbidder + +import ( + "net/http" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// IBidderMacro interface will capture all macro definition +type IBidderMacro interface { + //Helper Function + InitBidRequest(request *openrtb2.BidRequest) + LoadImpression(imp *openrtb2.Imp) (*openrtb_ext.ExtImpVASTBidder, error) + LoadVASTTag(tag *openrtb_ext.ExtImpVASTBidderTag) + GetBidderKeys() map[string]string + SetAdapterConfig(*config.Adapter) + GetURI() string + GetHeaders() http.Header + GetValue(string) (string, bool) + //getAllHeaders returns default and custom heades + getAllHeaders() http.Header + + //Request + MacroTest(string) string + MacroTimeout(string) string + MacroWhitelistSeat(string) string + MacroWhitelistLang(string) string + MacroBlockedSeat(string) string + MacroCurrency(string) string + MacroBlockedCategory(string) string + MacroBlockedAdvertiser(string) string + MacroBlockedApp(string) string + + //Source + MacroFD(string) string + MacroTransactionID(string) string + MacroPaymentIDChain(string) string + MacroSchain(string) string + + //Regs + MacroCoppa(string) string + + //Impression + MacroDisplayManager(string) string + MacroDisplayManagerVersion(string) string + MacroInterstitial(string) string + MacroTagID(string) string + MacroBidFloor(string) string + MacroBidFloorCurrency(string) string + MacroSecure(string) string + MacroPMP(string) string + + //Video + MacroVideoMIMES(string) string + MacroVideoMinimumDuration(string) string + MacroVideoMaximumDuration(string) string + MacroVideoProtocols(string) string + MacroVideoPlayerWidth(string) string + MacroVideoPlayerHeight(string) string + MacroVideoStartDelay(string) string + MacroVideoPlacement(string) string + MacroVideoLinearity(string) string + MacroVideoSkip(string) string + MacroVideoSkipMinimum(string) string + MacroVideoSkipAfter(string) string + MacroVideoSequence(string) string + MacroVideoBlockedAttribute(string) string + MacroVideoMaximumExtended(string) string + MacroVideoMinimumBitRate(string) string + MacroVideoMaximumBitRate(string) string + MacroVideoBoxing(string) string + MacroVideoPlaybackMethod(string) string + MacroVideoDelivery(string) string + MacroVideoPosition(string) string + MacroVideoAPI(string) string + + //Site + MacroSiteID(string) string + MacroSiteName(string) string + MacroSitePage(string) string + MacroSiteReferrer(string) string + MacroSiteSearch(string) string + MacroSiteMobile(string) string + + //App + MacroAppID(string) string + MacroAppName(string) string + MacroAppBundle(string) string + MacroAppStoreURL(string) string + MacroAppVersion(string) string + MacroAppPaid(string) string + + //SiteAppCommon + MacroCategory(string) string + MacroDomain(string) string + MacroSectionCategory(string) string + MacroPageCategory(string) string + MacroPrivacyPolicy(string) string + MacroKeywords(string) string + + //Publisher + MacroPubID(string) string + MacroPubName(string) string + MacroPubDomain(string) string + + //Content + MacroContentID(string) string + MacroContentEpisode(string) string + MacroContentTitle(string) string + MacroContentSeries(string) string + MacroContentSeason(string) string + MacroContentArtist(string) string + MacroContentGenre(string) string + MacroContentAlbum(string) string + MacroContentISrc(string) string + MacroContentURL(string) string + MacroContentCategory(string) string + MacroContentProductionQuality(string) string + MacroContentVideoQuality(string) string + MacroContentContext(string) string + MacroContentContentRating(string) string + MacroContentUserRating(string) string + MacroContentQAGMediaRating(string) string + MacroContentKeywords(string) string + MacroContentLiveStream(string) string + MacroContentSourceRelationship(string) string + MacroContentLength(string) string + MacroContentLanguage(string) string + MacroContentEmbeddable(string) string + + //Producer + MacroProducerID(string) string + MacroProducerName(string) string + + //Device + MacroUserAgent(string) string + MacroDNT(string) string + MacroLMT(string) string + MacroIP(string) string + MacroDeviceType(string) string + MacroMake(string) string + MacroModel(string) string + MacroDeviceOS(string) string + MacroDeviceOSVersion(string) string + MacroDeviceWidth(string) string + MacroDeviceHeight(string) string + MacroDeviceJS(string) string + MacroDeviceLanguage(string) string + MacroDeviceIFA(string) string + MacroDeviceIFAType(string) string + MacroDeviceDIDSHA1(string) string + MacroDeviceDIDMD5(string) string + MacroDeviceDPIDSHA1(string) string + MacroDeviceDPIDMD5(string) string + MacroDeviceMACSHA1(string) string + MacroDeviceMACMD5(string) string + + //Geo + MacroLatitude(string) string + MacroLongitude(string) string + MacroCountry(string) string + MacroRegion(string) string + MacroCity(string) string + MacroZip(string) string + MacroUTCOffset(string) string + + //User + MacroUserID(string) string + MacroYearOfBirth(string) string + MacroGender(string) string + + //Extension + MacroGDPRConsent(string) string + MacroGDPR(string) string + MacroUSPrivacy(string) string + + //Additional + MacroCacheBuster(string) string + + //Keyval + MacroKV(string) string + MacroKVM(string) string +} + +var bidderMacroMap = map[openrtb_ext.BidderName]func() IBidderMacro{} + +// RegisterNewBidderMacro will be used by each bidder to set its respective macro IBidderMacro +func RegisterNewBidderMacro(bidder openrtb_ext.BidderName, macro func() IBidderMacro) { + bidderMacroMap[bidder] = macro +} + +// GetNewBidderMacro will return IBidderMacro of specific bidder +func GetNewBidderMacro(bidder openrtb_ext.BidderName) IBidderMacro { + callback, ok := bidderMacroMap[bidder] + if ok { + return callback() + } + return NewBidderMacro() +} diff --git a/adapters/vastbidder/itag_response_handler.go b/adapters/vastbidder/itag_response_handler.go new file mode 100644 index 00000000000..022670bc026 --- /dev/null +++ b/adapters/vastbidder/itag_response_handler.go @@ -0,0 +1,43 @@ +package vastbidder + +import ( + "errors" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" +) + +// ITagRequestHandler parse bidder request +type ITagRequestHandler interface { + MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) +} + +// ITagResponseHandler parse bidder response +type ITagResponseHandler interface { + Validate(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) []error + MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) +} + +// HandlerType list of tag based response handlers +type HandlerType string + +const ( + VASTTagHandlerType HandlerType = `vasttag` +) + +// GetResponseHandler returns response handler +func GetResponseHandler(responseType HandlerType) (ITagResponseHandler, error) { + switch responseType { + case VASTTagHandlerType: + return NewVASTTagResponseHandler(), nil + } + return nil, errors.New(`Unkown Response Handler`) +} + +func GetRequestHandler(responseType HandlerType) (ITagRequestHandler, error) { + switch responseType { + case VASTTagHandlerType: + return nil, nil + } + return nil, errors.New(`Unkown Response Handler`) +} diff --git a/adapters/vastbidder/macro_processor.go b/adapters/vastbidder/macro_processor.go new file mode 100644 index 00000000000..c6f2f3d871e --- /dev/null +++ b/adapters/vastbidder/macro_processor.go @@ -0,0 +1,177 @@ +package vastbidder + +import ( + "bytes" + "net/url" + "strings" + + "github.com/golang/glog" +) + +const ( + macroPrefix string = `{` //macro prefix can not be empty + macroSuffix string = `}` //macro suffix can not be empty + macroEscapeSuffix string = `_ESC` + macroPrefixLen int = len(macroPrefix) + macroSuffixLen int = len(macroSuffix) + macroEscapeSuffixLen int = len(macroEscapeSuffix) +) + +//Flags to customize macro processing wrappers + +// MacroProcessor struct to hold openrtb request and cache values +type MacroProcessor struct { + bidderMacro IBidderMacro + mapper Mapper + macroCache map[string]string + bidderKeys map[string]string +} + +// NewMacroProcessor will process macro's of openrtb bid request +func NewMacroProcessor(bidderMacro IBidderMacro, mapper Mapper) *MacroProcessor { + return &MacroProcessor{ + bidderMacro: bidderMacro, + mapper: mapper, + macroCache: make(map[string]string), + } +} + +// SetMacro Adding Custom Macro Manually +func (mp *MacroProcessor) SetMacro(key, value string) { + mp.macroCache[key] = value +} + +// SetBidderKeys will flush and set bidder specific keys +func (mp *MacroProcessor) SetBidderKeys(keys map[string]string) { + mp.bidderKeys = keys +} + +// processKey : returns value of key macro and status found or not +func (mp *MacroProcessor) processKey(key string) (string, bool) { + var valueCallback *macroCallBack + var value string + nEscaping := 0 + tmpKey := key + found := false + + for { + //Search in macro cache + if value, found = mp.macroCache[tmpKey]; found { + break + } + + //Search for bidder keys + if nil != mp.bidderKeys { + if value, found = mp.bidderKeys[tmpKey]; found { + //default escaping of bidder keys + if len(value) > 0 && nEscaping == 0 { + //escape parameter only if _ESC is not present + value = url.QueryEscape(value) + } + break + } + } + + valueCallback, found = mp.mapper[tmpKey] + + if found { + //found callback function + value = valueCallback.callback(mp.bidderMacro, tmpKey) + //checking if default escaping needed or not + if len(value) > 0 && valueCallback.escape && nEscaping == 0 { + //escape parameter only if defaultescaping is true and _ESC is not present + value = url.QueryEscape(value) + } + break + } else if strings.HasSuffix(tmpKey, macroEscapeSuffix) { + //escaping macro found + tmpKey = tmpKey[0 : len(tmpKey)-macroEscapeSuffixLen] + nEscaping++ + continue + } else { + value, found = mp.bidderMacro.GetValue(tmpKey) + } + break + } + + if found { + if len(value) > 0 { + if nEscaping > 0 { + //escaping string nEscaping times + value = escape(value, nEscaping) + } + if nil != valueCallback && valueCallback.cached { + //cached value if its cached flag is true + mp.macroCache[key] = value + } + } + } + + return value, found +} + +// Process : Substitute macros in input string +func (mp *MacroProcessor) Process(in string) (response string) { + var out bytes.Buffer + pos, start, end, size := 0, 0, 0, len(in) + + for pos < size { + //find macro prefix index + if start = strings.Index(in[pos:], macroPrefix); -1 == start { + //[prefix_not_found] append remaining string to response + out.WriteString(in[pos:]) + + //macro prefix not found + break + } + + //prefix index w.r.t original string + start = start + pos + + //append non macro prefix content + out.WriteString(in[pos:start]) + + if (end - macroSuffixLen) <= (start + macroPrefixLen) { + //string contains {{TEXT_{{MACRO}} -> it should replace it with{{TEXT_MACROVALUE + //find macro suffix index + if end = strings.Index(in[start+macroPrefixLen:], macroSuffix); -1 == end { + //[suffix_not_found] append remaining string to response + out.WriteString(in[start:]) + + // We Found First %% and Not Found Second %% But We are in between of string + break + } + + end = start + macroPrefixLen + end + macroSuffixLen + } + + //get actual macro key by removing macroPrefix and macroSuffix from key itself + key := in[start+macroPrefixLen : end-macroSuffixLen] + + //process macro + value, found := mp.processKey(key) + if found { + out.WriteString(value) + pos = end + } else { + out.WriteByte(macroPrefix[0]) + pos = start + 1 + } + //glog.Infof("\nSearch[%d] : [%d,%d,%s]", count, start, end, key) + } + response = out.String() + glog.V(3).Infof("[MACRO]:in:[%s] replaced:[%s]", in, response) + return +} + +// GetMacroKey will return macro formatted key +func GetMacroKey(key string) string { + return macroPrefix + key + macroSuffix +} + +func escape(str string, n int) string { + for ; n > 0; n-- { + str = url.QueryEscape(str) + } + return str[:] +} diff --git a/adapters/vastbidder/macro_processor_test.go b/adapters/vastbidder/macro_processor_test.go new file mode 100644 index 00000000000..d4f77f43f8a --- /dev/null +++ b/adapters/vastbidder/macro_processor_test.go @@ -0,0 +1,304 @@ +package vastbidder + +import ( + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/stretchr/testify/assert" +) + +func TestMacroProcessor_Process(t *testing.T) { + bidRequestValues := map[string]string{ + MacroPubID: `pubID`, + MacroTagID: `tagid value`, + } + + testMacroValues := map[string]string{ + MacroPubID: `pubID`, + MacroTagID: `tagid+value`, //default escaping + MacroTagID + macroEscapeSuffix: `tagid+value`, //single escaping explicitly + MacroTagID + macroEscapeSuffix + macroEscapeSuffix: `tagid%2Bvalue`, + } + + sampleBidRequest := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {TagID: bidRequestValues[MacroTagID]}, + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: bidRequestValues[MacroPubID], + }, + }, + } + + tests := []struct { + name string + in string + expected string + }{ + { + name: "EmptyInput", + in: "", + expected: "", + }, + { + name: "NoMacroReplacement", + in: "Hello Test No Macro", + expected: "Hello Test No Macro", + }, + { + name: "StartMacro", + in: GetMacroKey(MacroTagID) + "HELLO", + expected: testMacroValues[MacroTagID] + "HELLO", + }, + { + name: "EndMacro", + in: "HELLO" + GetMacroKey(MacroTagID), + expected: "HELLO" + testMacroValues[MacroTagID], + }, + { + name: "StartEndMacro", + in: GetMacroKey(MacroTagID) + "HELLO" + GetMacroKey(MacroTagID), + expected: testMacroValues[MacroTagID] + "HELLO" + testMacroValues[MacroTagID], + }, + { + name: "HalfStartMacro", + in: macroPrefix + GetMacroKey(MacroTagID) + "HELLO", + expected: macroPrefix + testMacroValues[MacroTagID] + "HELLO", + }, + { + name: "HalfEndMacro", + in: "HELLO" + GetMacroKey(MacroTagID) + macroSuffix, + expected: "HELLO" + testMacroValues[MacroTagID] + macroSuffix, + }, + { + name: "ConcatenatedMacro", + in: GetMacroKey(MacroTagID) + GetMacroKey(MacroTagID) + "HELLO", + expected: testMacroValues[MacroTagID] + testMacroValues[MacroTagID] + "HELLO", + }, + { + name: "IncompleteConcatenationMacro", + in: GetMacroKey(MacroTagID) + macroSuffix + "LINKHELLO", + expected: testMacroValues[MacroTagID] + macroSuffix + "LINKHELLO", + }, + { + name: "ConcatenationWithSuffixMacro", + in: GetMacroKey(MacroTagID) + macroPrefix + GetMacroKey(MacroTagID) + "HELLO", + expected: testMacroValues[MacroTagID] + macroPrefix + testMacroValues[MacroTagID] + "HELLO", + }, + { + name: "UnknownMacro", + in: GetMacroKey(`UNKNOWN`) + `ABC`, + expected: GetMacroKey(`UNKNOWN`) + `ABC`, + }, + { + name: "IncompleteMacroSuffix", + in: "START" + macroSuffix, + expected: "START" + macroSuffix, + }, + { + name: "IncompleteStartAndEnd", + in: string(macroPrefix[0]) + GetMacroKey(MacroTagID) + " Value " + GetMacroKey(MacroTagID) + string(macroSuffix[0]), + expected: string(macroPrefix[0]) + testMacroValues[MacroTagID] + " Value " + testMacroValues[MacroTagID] + string(macroSuffix[0]), + }, + { + name: "SpecialCharacter", + in: macroPrefix + MacroTagID + `\n` + macroSuffix + "Sample \"" + GetMacroKey(MacroTagID) + "\" Data", + expected: macroPrefix + MacroTagID + `\n` + macroSuffix + "Sample \"" + testMacroValues[MacroTagID] + "\" Data", + }, + { + name: "EmptyValue", + in: GetMacroKey(MacroTimeout) + "Hello", + expected: "Hello", + }, + { + name: "EscapingMacro", + in: GetMacroKey(MacroTagID), + expected: testMacroValues[MacroTagID], + }, + { + name: "SingleEscapingMacro", + in: GetMacroKey(MacroTagID + macroEscapeSuffix), + expected: testMacroValues[MacroTagID+macroEscapeSuffix], + }, + { + name: "DoubleEscapingMacro", + in: GetMacroKey(MacroTagID + macroEscapeSuffix + macroEscapeSuffix), + expected: testMacroValues[MacroTagID+macroEscapeSuffix+macroEscapeSuffix], + }, + + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bidderMacro := NewBidderMacro() + mapper := GetDefaultMapper() + mp := NewMacroProcessor(bidderMacro, mapper) + + //Init Bidder Macro + bidderMacro.InitBidRequest(sampleBidRequest) + bidderMacro.LoadImpression(&sampleBidRequest.Imp[0]) + + gotResponse := mp.Process(tt.in) + assert.Equal(t, tt.expected, gotResponse) + }) + } +} + +func TestMacroProcessor_processKey(t *testing.T) { + bidRequestValues := map[string]string{ + MacroPubID: `1234`, + MacroTagID: `tagid value`, + } + + testMacroValues := map[string]string{ + MacroPubID: `1234`, + MacroPubID + macroEscapeSuffix: `1234`, + MacroTagID: `tagid+value`, + MacroTagID + macroEscapeSuffix: `tagid+value`, + MacroTagID + macroEscapeSuffix + macroEscapeSuffix: `tagid%2Bvalue`, + } + + sampleBidRequest := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {TagID: bidRequestValues[MacroTagID]}, + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: bidRequestValues[MacroPubID], + }, + }, + } + type args struct { + cache map[string]string + key string + } + type want struct { + expected string + ok bool + cache map[string]string + } + tests := []struct { + name string + args args + want want + }{ + { + name: `emptyKey`, + args: args{}, + want: want{ + expected: "", + ok: false, + cache: map[string]string{}, + }, + }, + { + name: `cachedKeyFound`, + args: args{ + cache: map[string]string{ + MacroPubID: testMacroValues[MacroPubID], + }, + key: MacroPubID, + }, + want: want{ + expected: testMacroValues[MacroPubID], + ok: true, + cache: map[string]string{ + MacroPubID: testMacroValues[MacroPubID], + }, + }, + }, + { + name: `valueFound`, + args: args{ + key: MacroTagID, + }, + want: want{ + expected: testMacroValues[MacroTagID], + ok: true, + cache: map[string]string{}, + }, + }, + { + name: `2TimesEscaping`, + args: args{ + key: MacroTagID + macroEscapeSuffix + macroEscapeSuffix, + }, + want: want{ + expected: testMacroValues[MacroTagID+macroEscapeSuffix+macroEscapeSuffix], + ok: true, + cache: map[string]string{}, + }, + }, + { + name: `macroNotPresent`, + args: args{ + key: `Unknown`, + }, + want: want{ + expected: "", + ok: false, + cache: map[string]string{}, + }, + }, + { + name: `macroNotPresentInEscaping`, + args: args{ + key: `Unknown` + macroEscapeSuffix, + }, + want: want{ + expected: "", + ok: false, + cache: map[string]string{}, + }, + }, + { + name: `cachedKey`, + args: args{ + key: MacroPubID, + }, + want: want{ + expected: testMacroValues[MacroPubID], + ok: true, + cache: map[string]string{ + MacroPubID: testMacroValues[MacroPubID], + }, + }, + }, + { + name: `cachedEscapingKey`, + args: args{ + key: MacroPubID + macroEscapeSuffix, + }, + want: want{ + expected: testMacroValues[MacroPubID+macroEscapeSuffix], + ok: true, + cache: map[string]string{ + MacroPubID + macroEscapeSuffix: testMacroValues[MacroPubID+macroEscapeSuffix], + }, + }, + }, + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bidderMacro := NewBidderMacro() + mapper := GetDefaultMapper() + mp := NewMacroProcessor(bidderMacro, mapper) + + //init bidder macro + bidderMacro.InitBidRequest(sampleBidRequest) + bidderMacro.LoadImpression(&sampleBidRequest.Imp[0]) + + //init cache of macro processor + if nil != tt.args.cache { + mp.macroCache = tt.args.cache + } + + actual, ok := mp.processKey(tt.args.key) + assert.Equal(t, tt.want.expected, actual) + assert.Equal(t, tt.want.ok, ok) + assert.Equal(t, tt.want.cache, mp.macroCache) + }) + } +} diff --git a/adapters/vastbidder/mapper.go b/adapters/vastbidder/mapper.go new file mode 100644 index 00000000000..c97ceb5e109 --- /dev/null +++ b/adapters/vastbidder/mapper.go @@ -0,0 +1,187 @@ +package vastbidder + +type macroCallBack struct { + cached bool + escape bool + callback func(IBidderMacro, string) string +} + +// Mapper will map macro with its respective call back function +type Mapper map[string]*macroCallBack + +func (obj Mapper) clone() Mapper { + cloned := make(Mapper, len(obj)) + for k, v := range obj { + newCallback := *v + cloned[k] = &newCallback + } + return cloned +} + +var _defaultMapper = Mapper{ + //Request + MacroTest: ¯oCallBack{cached: true, callback: IBidderMacro.MacroTest}, + MacroTimeout: ¯oCallBack{cached: true, callback: IBidderMacro.MacroTimeout}, + MacroWhitelistSeat: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroWhitelistSeat}, + MacroWhitelistLang: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroWhitelistLang}, + MacroBlockedSeat: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroBlockedSeat}, + MacroCurrency: ¯oCallBack{cached: true, callback: IBidderMacro.MacroCurrency}, + MacroBlockedCategory: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroBlockedCategory}, + MacroBlockedAdvertiser: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroBlockedAdvertiser}, + MacroBlockedApp: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroBlockedApp}, + + //Source + MacroFD: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroFD}, + MacroTransactionID: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroTransactionID}, + MacroPaymentIDChain: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroPaymentIDChain}, + MacroSchain: ¯oCallBack{cached: true, escape: false, callback: IBidderMacro.MacroSchain}, + + //Regs + MacroCoppa: ¯oCallBack{cached: true, callback: IBidderMacro.MacroCoppa}, + + //Impression + MacroDisplayManager: ¯oCallBack{cached: false, escape: true, callback: IBidderMacro.MacroDisplayManager}, + MacroDisplayManagerVersion: ¯oCallBack{cached: false, escape: true, callback: IBidderMacro.MacroDisplayManagerVersion}, + MacroInterstitial: ¯oCallBack{cached: false, callback: IBidderMacro.MacroInterstitial}, + MacroTagID: ¯oCallBack{cached: false, escape: true, callback: IBidderMacro.MacroTagID}, + MacroBidFloor: ¯oCallBack{cached: false, callback: IBidderMacro.MacroBidFloor}, + MacroBidFloorCurrency: ¯oCallBack{cached: false, callback: IBidderMacro.MacroBidFloorCurrency}, + MacroSecure: ¯oCallBack{cached: false, callback: IBidderMacro.MacroSecure}, + MacroPMP: ¯oCallBack{cached: false, escape: true, callback: IBidderMacro.MacroPMP}, + + //Video + MacroVideoMIMES: ¯oCallBack{cached: false, escape: true, callback: IBidderMacro.MacroVideoMIMES}, + MacroVideoMinimumDuration: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoMinimumDuration}, + MacroVideoMaximumDuration: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoMaximumDuration}, + MacroVideoProtocols: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoProtocols}, + MacroVideoPlayerWidth: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoPlayerWidth}, + MacroVideoPlayerHeight: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoPlayerHeight}, + MacroVideoStartDelay: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoStartDelay}, + MacroVideoPlacement: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoPlacement}, + MacroVideoLinearity: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoLinearity}, + MacroVideoSkip: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoSkip}, + MacroVideoSkipMinimum: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoSkipMinimum}, + MacroVideoSkipAfter: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoSkipAfter}, + MacroVideoSequence: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoSequence}, + MacroVideoBlockedAttribute: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoBlockedAttribute}, + MacroVideoMaximumExtended: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoMaximumExtended}, + MacroVideoMinimumBitRate: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoMinimumBitRate}, + MacroVideoMaximumBitRate: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoMaximumBitRate}, + MacroVideoBoxing: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoBoxing}, + MacroVideoPlaybackMethod: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoPlaybackMethod}, + MacroVideoDelivery: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoDelivery}, + MacroVideoPosition: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoPosition}, + MacroVideoAPI: ¯oCallBack{cached: false, callback: IBidderMacro.MacroVideoAPI}, + + //Site + MacroSiteID: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroSiteID}, + MacroSiteName: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroSiteName}, + MacroSitePage: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroSitePage}, + MacroSiteReferrer: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroSiteReferrer}, + MacroSiteSearch: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroSiteSearch}, + MacroSiteMobile: ¯oCallBack{cached: true, callback: IBidderMacro.MacroSiteMobile}, + + //App + MacroAppID: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroAppID}, + MacroAppName: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroAppName}, + MacroAppBundle: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroAppBundle}, + MacroAppStoreURL: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroAppStoreURL}, + MacroAppVersion: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroAppVersion}, + MacroAppPaid: ¯oCallBack{cached: true, callback: IBidderMacro.MacroAppPaid}, + + //SiteAppCommon + MacroCategory: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroCategory}, + MacroDomain: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroDomain}, + MacroSectionCategory: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroSectionCategory}, + MacroPageCategory: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroPageCategory}, + MacroPrivacyPolicy: ¯oCallBack{cached: true, callback: IBidderMacro.MacroPrivacyPolicy}, + MacroKeywords: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroKeywords}, + + //Publisher + MacroPubID: ¯oCallBack{cached: true, callback: IBidderMacro.MacroPubID}, + MacroPubName: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroPubName}, + MacroPubDomain: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroPubDomain}, + + //Content + MacroContentID: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentID}, + MacroContentEpisode: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentEpisode}, + MacroContentTitle: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentTitle}, + MacroContentSeries: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentSeries}, + MacroContentSeason: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentSeason}, + MacroContentArtist: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentArtist}, + MacroContentGenre: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentGenre}, + MacroContentAlbum: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentAlbum}, + MacroContentISrc: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentISrc}, + MacroContentURL: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentURL}, + MacroContentCategory: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentCategory}, + MacroContentProductionQuality: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentProductionQuality}, + MacroContentVideoQuality: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentVideoQuality}, + MacroContentContext: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentContext}, + MacroContentContentRating: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentContentRating}, + MacroContentUserRating: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentUserRating}, + MacroContentQAGMediaRating: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentQAGMediaRating}, + MacroContentKeywords: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentKeywords}, + MacroContentLiveStream: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentLiveStream}, + MacroContentSourceRelationship: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentSourceRelationship}, + MacroContentLength: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentLength}, + MacroContentLanguage: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroContentLanguage}, + MacroContentEmbeddable: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentEmbeddable}, + + //Producer + MacroProducerID: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroProducerID}, + MacroProducerName: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroProducerName}, + + //Device + MacroUserAgent: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroUserAgent}, + MacroDNT: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDNT}, + MacroLMT: ¯oCallBack{cached: true, callback: IBidderMacro.MacroLMT}, + MacroIP: ¯oCallBack{cached: true, callback: IBidderMacro.MacroIP}, + MacroDeviceType: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDeviceType}, + MacroMake: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroMake}, + MacroModel: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroModel}, + MacroDeviceOS: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroDeviceOS}, + MacroDeviceOSVersion: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroDeviceOSVersion}, + MacroDeviceWidth: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDeviceWidth}, + MacroDeviceHeight: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDeviceHeight}, + MacroDeviceJS: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDeviceJS}, + MacroDeviceLanguage: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroDeviceLanguage}, + MacroDeviceIFA: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroDeviceIFA}, + MacroDeviceIFAType: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroDeviceIFAType}, + MacroDeviceDIDSHA1: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroDeviceDIDSHA1}, + MacroDeviceDIDMD5: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroDeviceDIDMD5}, + MacroDeviceDPIDSHA1: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroDeviceDPIDSHA1}, + MacroDeviceDPIDMD5: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroDeviceDPIDMD5}, + MacroDeviceMACSHA1: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroDeviceMACSHA1}, + MacroDeviceMACMD5: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroDeviceMACMD5}, + + //Geo + MacroLatitude: ¯oCallBack{cached: true, callback: IBidderMacro.MacroLatitude}, + MacroLongitude: ¯oCallBack{cached: true, callback: IBidderMacro.MacroLongitude}, + MacroCountry: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroCountry}, + MacroRegion: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroRegion}, + MacroCity: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroCity}, + MacroZip: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroZip}, + MacroUTCOffset: ¯oCallBack{cached: true, callback: IBidderMacro.MacroUTCOffset}, + + //User + MacroUserID: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroUserID}, + MacroYearOfBirth: ¯oCallBack{cached: true, callback: IBidderMacro.MacroYearOfBirth}, + MacroGender: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroGender}, + + //Extension + MacroGDPRConsent: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroGDPRConsent}, + MacroGDPR: ¯oCallBack{cached: true, callback: IBidderMacro.MacroGDPR}, + MacroUSPrivacy: ¯oCallBack{cached: true, escape: true, callback: IBidderMacro.MacroUSPrivacy}, + + //Additional + MacroCacheBuster: ¯oCallBack{cached: false, callback: IBidderMacro.MacroCacheBuster}, + + //KeyVal + MacroKV: ¯oCallBack{cached: false, callback: IBidderMacro.MacroKV}, + MacroKVM: ¯oCallBack{cached: false, callback: IBidderMacro.MacroKVM}, +} + +// GetDefaultMapper will return clone of default Mapper function +func GetDefaultMapper() Mapper { + return _defaultMapper.clone() +} diff --git a/adapters/vastbidder/sample_spotx_macro.go.bak b/adapters/vastbidder/sample_spotx_macro.go.bak new file mode 100644 index 00000000000..6c04861cf40 --- /dev/null +++ b/adapters/vastbidder/sample_spotx_macro.go.bak @@ -0,0 +1,28 @@ +package vastbidder + +import ( + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +//SpotxMacro default implementation +type SpotxMacro struct { + *BidderMacro +} + +//NewSpotxMacro contains definition for all openrtb macro's +func NewSpotxMacro() IBidderMacro { + obj := &SpotxMacro{ + BidderMacro: &BidderMacro{}, + } + obj.IBidderMacro = obj + return obj +} + +//GetBidderKeys will set bidder level keys +func (tag *SpotxMacro) GetBidderKeys() map[string]string { + return NormalizeJSON(tag.ImpBidderExt) +} + +func init() { + RegisterNewBidderMacro(openrtb_ext.BidderSpotX, NewSpotxMacro) +} diff --git a/adapters/vastbidder/tagbidder.go b/adapters/vastbidder/tagbidder.go new file mode 100644 index 00000000000..85e427afbae --- /dev/null +++ b/adapters/vastbidder/tagbidder.go @@ -0,0 +1,87 @@ +package vastbidder + +import ( + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// TagBidder is default implementation of ITagBidder +type TagBidder struct { + adapters.Bidder + bidderName openrtb_ext.BidderName + adapterConfig *config.Adapter +} + +// MakeRequests will contains default definition for processing queries +func (a *TagBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + bidderMacro := GetNewBidderMacro(a.bidderName) + bidderMapper := GetDefaultMapper() + macroProcessor := NewMacroProcessor(bidderMacro, bidderMapper) + + //Setting config parameters + //bidderMacro.SetBidderConfig(a.bidderConfig) + bidderMacro.SetAdapterConfig(a.adapterConfig) + bidderMacro.InitBidRequest(request) + + requestData := []*adapters.RequestData{} + for impIndex := range request.Imp { + bidderExt, err := bidderMacro.LoadImpression(&request.Imp[impIndex]) + if nil != err { + continue + } + + //iterate each vast tags, and load vast tag + for vastTagIndex, tag := range bidderExt.Tags { + //load vasttag + bidderMacro.LoadVASTTag(tag) + + //Setting Bidder Level Keys + bidderKeys := bidderMacro.GetBidderKeys() + macroProcessor.SetBidderKeys(bidderKeys) + + uri := macroProcessor.Process(bidderMacro.GetURI()) + + // append custom headers if any + headers := bidderMacro.getAllHeaders() + + requestData = append(requestData, &adapters.RequestData{ + Params: &adapters.BidRequestParams{ + ImpIndex: impIndex, + VASTTagIndex: vastTagIndex, + }, + Method: `GET`, + Uri: uri, + Headers: headers, + }) + } + } + + return requestData, nil +} + +// MakeBids makes bids +func (a *TagBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + //response validation can be done here independently + //handler, err := GetResponseHandler(a.bidderConfig.ResponseType) + handler, err := GetResponseHandler(VASTTagHandlerType) + if nil != err { + return nil, []error{err} + } + return handler.MakeBids(internalRequest, externalRequest, response) +} + +// NewTagBidder is an constructor for TagBidder +func NewTagBidder(bidderName openrtb_ext.BidderName, config config.Adapter) *TagBidder { + obj := &TagBidder{ + bidderName: bidderName, + adapterConfig: &config, + } + return obj +} + +// Builder builds a new instance of the 33Across adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + return NewTagBidder(bidderName, config), nil +} diff --git a/adapters/vastbidder/tagbidder_test.go b/adapters/vastbidder/tagbidder_test.go new file mode 100644 index 00000000000..9a4c492e092 --- /dev/null +++ b/adapters/vastbidder/tagbidder_test.go @@ -0,0 +1,150 @@ +package vastbidder + +import ( + "net/http" + "testing" + + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +// TestMakeRequests verifies +// 1. default and custom headers are set +func TestMakeRequests(t *testing.T) { + + type args struct { + customHeaders map[string]string + req *openrtb2.BidRequest + } + type want struct { + impIDReqHeaderMap map[string]http.Header + } + tests := []struct { + name string + args args + want want + }{ + { + name: "multi_impression_req", + args: args{ + customHeaders: map[string]string{ + "my-custom-header": "custom-value", + }, + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + IP: "1.1.1.1", + UA: "user-agent", + Language: "en", + }, + Site: &openrtb2.Site{ + Page: "http://test.com/", + }, + Imp: []openrtb2.Imp{ + { // vast 2.0 + ID: "vast_2_0_imp_req", + Video: &openrtb2.Video{ + Protocols: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST20, + }, + }, + Ext: []byte(`{"bidder" :{}}`), + }, + { + ID: "vast_4_0_imp_req", + Video: &openrtb2.Video{ // vast 4.0 + Protocols: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST40, + }, + }, + Ext: []byte(`{"bidder" :{}}`), + }, + { + ID: "vast_2_0_4_0_wrapper_imp_req", + Video: &openrtb2.Video{ // vast 2 and 4.0 wrapper + Protocols: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST40Wrapper, + adcom1.CreativeVAST20, + }, + }, + Ext: []byte(`{"bidder" :{}}`), + }, + { + ID: "other_non_vast_protocol", + Video: &openrtb2.Video{ // DAAST 1.0 + Protocols: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeDAAST10, + }, + }, + Ext: []byte(`{"bidder" :{}}`), + }, + { + + ID: "no_protocol_field_set", + Video: &openrtb2.Video{ // vast 2 and 4.0 wrapper + Protocols: []adcom1.MediaCreativeSubtype{}, + }, + Ext: []byte(`{"bidder" :{}}`), + }, + }, + }, + }, + want: want{ + impIDReqHeaderMap: map[string]http.Header{ + "vast_2_0_imp_req": { + "X-Forwarded-For": []string{"1.1.1.1"}, + "User-Agent": []string{"user-agent"}, + "My-Custom-Header": []string{"custom-value"}, + }, + "vast_4_0_imp_req": { + "X-Device-Ip": []string{"1.1.1.1"}, + "X-Device-User-Agent": []string{"user-agent"}, + "X-Device-Referer": []string{"http://test.com/"}, + "X-Device-Accept-Language": []string{"en"}, + "My-Custom-Header": []string{"custom-value"}, + }, + "vast_2_0_4_0_wrapper_imp_req": { + "X-Device-Ip": []string{"1.1.1.1"}, + "X-Forwarded-For": []string{"1.1.1.1"}, + "X-Device-User-Agent": []string{"user-agent"}, + "User-Agent": []string{"user-agent"}, + "X-Device-Referer": []string{"http://test.com/"}, + "X-Device-Accept-Language": []string{"en"}, + "My-Custom-Header": []string{"custom-value"}, + }, + "other_non_vast_protocol": { + "My-Custom-Header": []string{"custom-value"}, + }, // no default headers expected + "no_protocol_field_set": { // set all default headers + "X-Device-Ip": []string{"1.1.1.1"}, + "X-Forwarded-For": []string{"1.1.1.1"}, + "X-Device-User-Agent": []string{"user-agent"}, + "User-Agent": []string{"user-agent"}, + "X-Device-Referer": []string{"http://test.com/"}, + "X-Device-Accept-Language": []string{"en"}, + "My-Custom-Header": []string{"custom-value"}, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bidderName := openrtb_ext.BidderName("myVastBidderMacro") + RegisterNewBidderMacro(bidderName, func() IBidderMacro { + return newMyVastBidderMacro(tt.args.customHeaders) + }) + bidder := NewTagBidder(bidderName, config.Adapter{}) + reqData, err := bidder.MakeRequests(tt.args.req, nil) + assert.Nil(t, err) + for _, req := range reqData { + impID := tt.args.req.Imp[req.Params.ImpIndex].ID + expectedHeaders := tt.want.impIDReqHeaderMap[impID] + assert.Equal(t, expectedHeaders, req.Headers, "test for - "+impID) + } + }) + } +} diff --git a/adapters/vastbidder/util.go b/adapters/vastbidder/util.go new file mode 100644 index 00000000000..6cff6167ed3 --- /dev/null +++ b/adapters/vastbidder/util.go @@ -0,0 +1,134 @@ +package vastbidder + +import ( + "bytes" + "encoding/json" + "fmt" + "math/rand" + "net/url" + "reflect" + "strconv" + "strings" + + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func ObjectArrayToString(len int, separator string, cb func(i int) string) string { + if 0 == len { + return "" + } + + var out bytes.Buffer + for i := 0; i < len; i++ { + if out.Len() > 0 { + out.WriteString(separator) + } + out.WriteString(cb(i)) + } + return out.String() +} + +func readImpExt(impExt json.RawMessage) (*openrtb_ext.ExtImpVASTBidder, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(impExt, &bidderExt); err != nil { + return nil, err + } + + vastBidderExt := openrtb_ext.ExtImpVASTBidder{} + if err := json.Unmarshal(bidderExt.Bidder, &vastBidderExt); err != nil { + return nil, err + } + return &vastBidderExt, nil +} + +func normalizeObject(prefix string, out map[string]string, obj map[string]interface{}) { + for k, value := range obj { + key := k + if len(prefix) > 0 { + key = prefix + "." + k + } + + switch val := value.(type) { + case string: + out[key] = val + case []interface{}: //array + continue + case map[string]interface{}: //object + normalizeObject(key, out, val) + default: //all int, float + out[key] = fmt.Sprint(value) + } + } +} + +func NormalizeJSON(obj map[string]interface{}) map[string]string { + out := map[string]string{} + normalizeObject("", out, obj) + return out +} + +var GetRandomID = func() string { + return strconv.FormatInt(rand.Int63(), intBase) +} + +func getJSONString(kvmap any) string { + + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + + // Disable HTML escaping for special characters + encoder.SetEscapeHTML(false) + + if err := encoder.Encode(kvmap); err != nil { + return "" + } + return strings.TrimRight(buf.String(), "\n") + +} + +func isMap(data any) bool { + return reflect.TypeOf(data).Kind() == reflect.Map +} + +// extractDataFromMap help to get value from nested map +func getValueFromMap(lookUpOrder []string, m map[string]any) any { + if len(lookUpOrder) == 0 { + return "" + } + + for _, key := range lookUpOrder { + value, keyExists := m[key] + if !keyExists { + return "" + } + if nestedMap, isMap := value.(map[string]any); isMap { + m = nestedMap + } else { + return value + } + } + return m +} + +// mapToQuery convert the map data into & seperated string +func mapToQuery(m map[string]any) string { + values := url.Values{} + for key, value := range m { + switch reflect.TypeOf(value).Kind() { + case reflect.Map: + mvalue, ok := value.(map[string]any) + if ok { + values.Add(key, mapToQuery(mvalue)) + } + default: + v := fmt.Sprintf("%v", value) + decodedString, err := url.QueryUnescape(v) + if err == nil { + v = decodedString + } + values.Add(key, v) + } + } + return values.Encode() +} diff --git a/adapters/vastbidder/util_test.go b/adapters/vastbidder/util_test.go new file mode 100644 index 00000000000..c011698d4a9 --- /dev/null +++ b/adapters/vastbidder/util_test.go @@ -0,0 +1,195 @@ +package vastbidder + +import ( + "testing" + + "github.com/magiconair/properties/assert" +) + +func Test_getJSONString(t *testing.T) { + type args struct { + kvmap any + } + tests := []struct { + name string + args args + want string + }{ + { + name: "empty_map", + args: args{kvmap: map[string]any{}}, + want: "{}", + }, + { + name: "map_without_nesting", + args: args{kvmap: map[string]any{ + "k1": "v1", + "k2": "v2", + }}, + want: "{\"k1\":\"v1\",\"k2\":\"v2\"}", + }, + { + name: "map_with_nesting", + args: args{kvmap: map[string]any{ + "k1": "v1", + "metadata": map[string]any{ + "k2": "v2", + "k3": "v3", + }, + }}, + want: "{\"k1\":\"v1\",\"metadata\":{\"k2\":\"v2\",\"k3\":\"v3\"}}", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getJSONString(tt.args.kvmap) + assert.Equal(t, got, tt.want, tt.name) + }) + } +} + +func Test_getValueFromMap(t *testing.T) { + type args struct { + lookUpOrder []string + m map[string]any + } + tests := []struct { + name string + args args + want any + }{ + { + name: "map_without_nesting", + args: args{lookUpOrder: []string{"k1"}, + m: map[string]any{ + "k1": "v1", + "k2": "v2", + }, + }, + want: "v1", + }, + { + name: "map_with_nesting", + args: args{lookUpOrder: []string{"country", "state"}, + m: map[string]any{ + "name": "test", + "country": map[string]any{ + "state": "MH", + "pin": 12345, + }, + }, + }, + want: "MH", + }, + { + name: "key_not_exists", + args: args{lookUpOrder: []string{"country", "name"}, + m: map[string]any{ + "name": "test", + "country": map[string]any{ + "state": "MH", + "pin": 12345, + }, + }, + }, + want: "", + }, + { + name: "empty_map", + args: args{lookUpOrder: []string{"country", "name"}, + m: map[string]any{}, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getValueFromMap(tt.args.lookUpOrder, tt.args.m) + assert.Equal(t, got, tt.want) + }) + } +} + +func Test_mapToQuery(t *testing.T) { + type args struct { + m map[string]any + } + tests := []struct { + name string + args args + want string + }{ + { + name: "map_without_nesting", + args: args{ + m: map[string]any{ + "k1": "v1", + "k2": "v2", + }, + }, + want: "k1=v1&k2=v2", + }, + { + name: "map_with_nesting", + args: args{ + m: map[string]any{ + "name": "test", + "country": map[string]any{ + "state": "MH", + "pin": 12345, + }, + }, + }, + want: "country=pin%3D12345%26state%3DMH&name=test", + }, + { + name: "empty_map", + args: args{ + m: map[string]any{}, + }, + want: "", + }, + { + name: "nil_map", + args: args{ + m: nil, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := mapToQuery(tt.args.m); got != tt.want { + t.Errorf("mapToQuery() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_isMap(t *testing.T) { + type args struct { + data any + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "map_data_type", + args: args{data: map[string]any{}}, + want: true, + }, + { + name: "string_data_type", + args: args{data: "data type is string"}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isMap(tt.args.data) + assert.Equal(t, got, tt.want) + }) + } +} diff --git a/adapters/vastbidder/vast_tag_response_handler.go b/adapters/vastbidder/vast_tag_response_handler.go new file mode 100644 index 00000000000..40fd345bcef --- /dev/null +++ b/adapters/vastbidder/vast_tag_response_handler.go @@ -0,0 +1,347 @@ +package vastbidder + +import ( + "encoding/json" + "errors" + "net/http" + "regexp" + "strconv" + "strings" + "time" + + "github.com/beevik/etree" + "github.com/golang/glog" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +var durationRegExp = regexp.MustCompile(`^([01]?\d|2[0-3]):([0-5]?\d):([0-5]?\d)(\.(\d{1,3}))?$`) + +// IVASTTagResponseHandler to parse VAST Tag +type IVASTTagResponseHandler interface { + ITagResponseHandler + ParseExtension(version string, tag *etree.Element, bid *adapters.TypedBid) []error + GetStaticPrice(ext json.RawMessage) float64 +} + +// VASTTagResponseHandler to parse VAST Tag +type VASTTagResponseHandler struct { + IVASTTagResponseHandler + ImpBidderExt *openrtb_ext.ExtImpVASTBidder + VASTTag *openrtb_ext.ExtImpVASTBidderTag +} + +// NewVASTTagResponseHandler returns new object +func NewVASTTagResponseHandler() *VASTTagResponseHandler { + obj := &VASTTagResponseHandler{} + obj.IVASTTagResponseHandler = obj + return obj +} + +// Validate will return bids +func (handler *VASTTagResponseHandler) Validate(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) []error { + if response.StatusCode != http.StatusOK { + return []error{errors.New(`validation failed`)} + } + + if len(internalRequest.Imp) < externalRequest.Params.ImpIndex { + return []error{errors.New(`validation failed invalid impression index`)} + } + + impExt, err := readImpExt(internalRequest.Imp[externalRequest.Params.ImpIndex].Ext) + if nil != err { + return []error{err} + } + + if len(impExt.Tags) < externalRequest.Params.VASTTagIndex { + return []error{errors.New(`validation failed invalid vast tag index`)} + } + + //Initialise Extensions + handler.ImpBidderExt = impExt + handler.VASTTag = impExt.Tags[externalRequest.Params.VASTTagIndex] + return nil +} + +// MakeBids will return bids +func (handler *VASTTagResponseHandler) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if err := handler.IVASTTagResponseHandler.Validate(internalRequest, externalRequest, response); len(err) > 0 { + return nil, err[:] + } + + bidResponses, err := handler.vastTagToBidderResponse(internalRequest, externalRequest, response) + return bidResponses, err +} + +// ParseExtension will parse VAST XML extension object +func (handler *VASTTagResponseHandler) ParseExtension(version string, ad *etree.Element, bid *adapters.TypedBid) []error { + return nil +} + +func (handler *VASTTagResponseHandler) vastTagToBidderResponse(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + doc := etree.NewDocument() + + //Read Document + if err := doc.ReadFromBytes(response.Body); err != nil { + errs = append(errs, err) + return nil, errs[:] + } + + //Check VAST Tag + vast := doc.Element.FindElement(`./VAST`) + if vast == nil { + errs = append(errs, errors.New("VAST Tag Not Found")) + return nil, errs[:] + } + + //Check VAST/Ad Tag + adElement := getAdElement(vast) + if nil == adElement { + errs = append(errs, errors.New("VAST/Ad Tag Not Found")) + return nil, errs[:] + } + + typedBid := &adapters.TypedBid{ + Bid: &openrtb2.Bid{}, + BidType: openrtb_ext.BidTypeVideo, + BidVideo: &openrtb_ext.ExtBidPrebidVideo{ + VASTTagID: handler.VASTTag.TagID, + }, + } + + creatives := adElement.FindElements("Creatives/Creative") + if nil != creatives { + for _, creative := range creatives { + // get creative id + typedBid.Bid.CrID = getCreativeID(creative) + + // get duration from vast creative + dur, err := getDuration(creative) + if nil != err { + // get duration from input bidder vast tag + dur = getStaticDuration(handler.VASTTag) + } + if dur > 0 { + typedBid.BidVideo.Duration = int(dur) // prebid expects int value + } + } + } + + bidResponse := &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{typedBid}, + Currency: `USD`, //TODO: Need to check how to get currency value + } + + //GetVersion + version := vast.SelectAttrValue(`version`, `2.0`) + + if err := handler.IVASTTagResponseHandler.ParseExtension(version, adElement, typedBid); len(err) > 0 { + errs = append(errs, err...) + return nil, errs[:] + } + + //if bid.price is not set in ParseExtension + if typedBid.Bid.Price <= 0 { + price, currency := getPricingDetails(version, adElement) + if price <= 0 { + price, currency = getStaticPricingDetails(handler.VASTTag) + if price <= 0 { + errs = append(errs, &errortypes.NoBidPrice{Message: "Bid Price Not Present"}) + return nil, errs[:] + } + } + typedBid.Bid.Price = price + if len(currency) > 0 { + bidResponse.Currency = currency + } + } + + typedBid.Bid.ADomain = getAdvertisers(version, adElement) + + //if bid.id is not set in ParseExtension + if len(typedBid.Bid.ID) == 0 { + typedBid.Bid.ID = GetRandomID() + } + + //if bid.impid is not set in ParseExtension + if len(typedBid.Bid.ImpID) == 0 { + typedBid.Bid.ImpID = internalRequest.Imp[externalRequest.Params.ImpIndex].ID + } + + //if bid.adm is not set in ParseExtension + if len(typedBid.Bid.AdM) == 0 { + typedBid.Bid.AdM = string(response.Body) + } + + //if bid.CrID is not set in ParseExtension + if len(typedBid.Bid.CrID) == 0 { + typedBid.Bid.CrID = "cr_" + GetRandomID() + } + + // set vastTagId in bid.Ext + bidExt := openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Video: typedBid.BidVideo, + Type: typedBid.BidType, + }, + } + + bidExtBytes, err := json.Marshal(bidExt) + if err == nil { + typedBid.Bid.Ext = bidExtBytes + } + + return bidResponse, nil +} + +func getAdElement(vast *etree.Element) *etree.Element { + if ad := vast.FindElement(`./Ad/Wrapper`); nil != ad { + return ad + } + if ad := vast.FindElement(`./Ad/InLine`); nil != ad { + return ad + } + return nil +} + +func getAdvertisers(vastVer string, ad *etree.Element) []string { + version, err := strconv.ParseFloat(vastVer, 64) + if err != nil { + version = 2.0 + } + + advertisers := make([]string, 0) + + switch int(version) { + case 2, 3: + for _, ext := range ad.FindElements(`./Extensions/Extension/`) { + for _, attr := range ext.Attr { + if attr.Key == "type" && attr.Value == "advertiser" { + for _, ele := range ext.ChildElements() { + if ele.Tag == "Advertiser" { + if strings.TrimSpace(ele.Text()) != "" { + advertisers = append(advertisers, ele.Text()) + } + } + } + } + } + } + case 4: + if ad.FindElement("./Advertiser") != nil { + adv := strings.TrimSpace(ad.FindElement("./Advertiser").Text()) + if adv != "" { + advertisers = append(advertisers, adv) + } + } + default: + glog.V(3).Infof("Handle getAdvertisers for VAST version %d", int(version)) + } + + if len(advertisers) == 0 { + return nil + } + return advertisers +} + +func getStaticPricingDetails(vastTag *openrtb_ext.ExtImpVASTBidderTag) (float64, string) { + if nil == vastTag { + return 0.0, "" + } + return vastTag.Price, "USD" +} + +func getPricingDetails(version string, ad *etree.Element) (float64, string) { + var currency string + var node *etree.Element + + if version == `2.0` { + node = ad.FindElement(`./Extensions/Extension/Price`) + } else { + node = ad.FindElement(`./Pricing`) + } + + if node == nil { + return 0.0, currency + } + + priceValue, err := strconv.ParseFloat(node.Text(), 64) + if nil != err { + return 0.0, currency + } + + currencyNode := node.SelectAttr(`currency`) + if nil != currencyNode { + currency = currencyNode.Value + } + + return priceValue, currency +} + +// getDuration extracts the duration of the bid from input creative of Linear type. +// The lookup may vary from vast version provided in the input +// returns duration in seconds or error if failed to obtained the duration. +// If multple Linear tags are present, onlyfirst one will be used +// +// It will lookup for duration only in case of creative type is Linear. +// If creative type other than Linear then this function will return error +// For Linear Creative it will lookup for Duration attribute.Duration value will be in hh:mm:ss.mmm format as per VAST specifications +// If Duration attribute not present this will return error +// +// # After extracing the duration it will convert it into seconds +// +// The ad server uses the element to denote +// the intended playback duration for the video or audio component of the ad. +// Time value may be in the format HH:MM:SS.mmm where .mmm indicates milliseconds. +// Providing milliseconds is optional. +// +// Reference +// 1.https://iabtechlab.com/wp-content/uploads/2019/06/VAST_4.2_final_june26.pdf +// 2.https://iabtechlab.com/wp-content/uploads/2018/11/VAST4.1-final-Nov-8-2018.pdf +// 3.https://iabtechlab.com/wp-content/uploads/2016/05/VAST4.0_Updated_April_2016.pdf +// 4.https://iabtechlab.com/wp-content/uploads/2016/04/VASTv3_0.pdf +func getDuration(creative *etree.Element) (int, error) { + if nil == creative { + return 0, errors.New("Invalid Creative") + } + node := creative.FindElement("./Linear/Duration") + if nil == node { + return 0, errors.New("Invalid Duration") + } + duration := node.Text() + // check if milliseconds is provided + match := durationRegExp.FindStringSubmatch(duration) + if nil == match { + return 0, errors.New("Invalid Duration") + } + repl := "${1}h${2}m${3}s" + ms := match[5] + if "" != ms { + repl += "${5}ms" + } + duration = durationRegExp.ReplaceAllString(duration, repl) + dur, err := time.ParseDuration(duration) + if err != nil { + return 0, err + } + return int(dur.Seconds()), nil +} + +func getStaticDuration(vastTag *openrtb_ext.ExtImpVASTBidderTag) int { + if nil == vastTag { + return 0 + } + return vastTag.Duration +} + +// getCreativeID looks for ID inside input creative tag +func getCreativeID(creative *etree.Element) string { + if nil == creative { + return "" + } + return creative.SelectAttrValue("id", "") +} diff --git a/adapters/vastbidder/vast_tag_response_handler_test.go b/adapters/vastbidder/vast_tag_response_handler_test.go new file mode 100644 index 00000000000..01547b361e1 --- /dev/null +++ b/adapters/vastbidder/vast_tag_response_handler_test.go @@ -0,0 +1,387 @@ +package vastbidder + +import ( + "encoding/json" + "errors" + "fmt" + "sort" + "testing" + + "github.com/beevik/etree" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestVASTTagResponseHandler_vastTagToBidderResponse(t *testing.T) { + type args struct { + internalRequest *openrtb2.BidRequest + externalRequest *adapters.RequestData + response *adapters.ResponseData + vastTag *openrtb_ext.ExtImpVASTBidderTag + } + type want struct { + bidderResponse *adapters.BidderResponse + err []error + } + tests := []struct { + name string + args args + want want + }{ + { + name: `InlinePricingNode`, + args: args{ + internalRequest: &openrtb2.BidRequest{ + ID: `request_id_1`, + Imp: []openrtb2.Imp{ + { + ID: `imp_id_1`, + }, + }, + }, + externalRequest: &adapters.RequestData{ + Params: &adapters.BidRequestParams{ + ImpIndex: 0, + }, + }, + response: &adapters.ResponseData{ + Body: []byte(` `), + }, + vastTag: &openrtb_ext.ExtImpVASTBidderTag{ + TagID: "101", + Duration: 15, + }, + }, + want: want{ + bidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: `1234`, + ImpID: `imp_id_1`, + Price: 0.05, + AdM: ` `, + CrID: "cr_1234", + Ext: json.RawMessage(`{"prebid":{"type":"video","video":{"duration":15,"primary_category":"","vasttagid":"101"}}}`), + }, + BidType: openrtb_ext.BidTypeVideo, + BidVideo: &openrtb_ext.ExtBidPrebidVideo{ + VASTTagID: "101", + Duration: 15, + }, + }, + }, + Currency: `USD`, + }, + }, + }, + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + handler := NewVASTTagResponseHandler() + GetRandomID = func() string { + return `1234` + } + handler.VASTTag = tt.args.vastTag + + bidderResponse, err := handler.vastTagToBidderResponse(tt.args.internalRequest, tt.args.externalRequest, tt.args.response) + assert.Equal(t, tt.want.bidderResponse, bidderResponse) + assert.Equal(t, tt.want.err, err) + }) + } +} + +// TestGetDurationInSeconds ... +// hh:mm:ss.mmm => 3:40:43.5 => 3 hours, 40 minutes, 43 seconds and 5 milliseconds +// => 3*60*60 + 40*60 + 43 + 5*0.001 => 10800 + 2400 + 43 + 0.005 => 13243.005 +func TestGetDurationInSeconds(t *testing.T) { + type args struct { + creativeTag string // ad element + } + type want struct { + duration int // seconds (will converted from string with format as HH:MM:SS.mmm) + err error + } + tests := []struct { + name string + args args + want want + }{ + // duration validation tests + {name: "duration 00:00:25 (= 25 seconds)", want: want{duration: 25}, args: args{creativeTag: ` 00:00:25 `}}, + {name: "duration 00:00:-25 (= -25 seconds)", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ` 00:00:-25 `}}, + {name: "duration 00:00:30.999 (= 30.990 seconds (int -> 30 seconds))", want: want{duration: 30}, args: args{creativeTag: ` 00:00:30.999 `}}, + {name: "duration 00:01:08 (1 min 8 seconds = 68 seconds)", want: want{duration: 68}, args: args{creativeTag: ` 00:01:08 `}}, + {name: "duration 02:13:12 (2 hrs 13 min 12 seconds) = 7992 seconds)", want: want{duration: 7992}, args: args{creativeTag: ` 02:13:12 `}}, + {name: "duration 3:40:43.5 (3 hrs 40 min 43 seconds 5 ms) = 6043.005 seconds (int -> 6043 seconds))", want: want{duration: 13243}, args: args{creativeTag: ` 3:40:43.5 `}}, + {name: "duration 00:00:25.0005458 (0 hrs 0 min 25 seconds 0005458 ms) - invalid max ms is 999", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ` 00:00:25.0005458 `}}, + {name: "invalid duration 3:13:900 (3 hrs 13 min 900 seconds) = Invalid seconds )", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ` 3:13:900 `}}, + {name: "invalid duration 3:13:34:44 (3 hrs 13 min 34 seconds :44=invalid) = ?? )", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ` 3:13:34:44 `}}, + {name: "duration = 0:0:45.038 , with milliseconds duration (0 hrs 0 min 45 seconds and 038 millseconds) = 45.038 seconds (int -> 45 seconds) )", want: want{duration: 45}, args: args{creativeTag: ` 0:0:45.038 `}}, + {name: "duration = 0:0:48.50 = 48.050 seconds (int -> 48 seconds))", want: want{duration: 48}, args: args{creativeTag: ` 0:0:48.50 `}}, + {name: "duration = 0:0:28.59 = 28.059 seconds (int -> 28 seconds))", want: want{duration: 28}, args: args{creativeTag: ` 0:0:28.59 `}}, + {name: "duration = 56 (ambiguity w.r.t. HH:MM:SS.mmm format)", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ` 56 `}}, + {name: "duration = :56 (ambiguity w.r.t. HH:MM:SS.mmm format)", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ` :56 `}}, + {name: "duration = :56: (ambiguity w.r.t. HH:MM:SS.mmm format)", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ` :56: `}}, + {name: "duration = ::56 (ambiguity w.r.t. HH:MM:SS.mmm format)", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ` ::56 `}}, + {name: "duration = 56.445 (ambiguity w.r.t. HH:MM:SS.mmm format)", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ` 56.445 `}}, + {name: "duration = a:b:c.d (no numbers)", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ` a:b:c.d `}}, + + // tag validations tests + {name: "Linear Creative no duration", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ``}}, + {name: "Companion Creative no duration", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ``}}, + {name: "Non-Linear Creative no duration", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: ``}}, + {name: "Invalid Creative tag", want: want{err: errors.New("Invalid Creative")}, args: args{creativeTag: ``}}, + {name: "Nil Creative tag", want: want{err: errors.New("Invalid Creative")}, args: args{creativeTag: ""}}, + + // multiple linear tags in creative + {name: "Multiple Linear Ads within Creative", want: want{duration: 25}, args: args{creativeTag: `0:0:250:0:30`}}, + // Case sensitivity check - passing DURATION (vast is case-sensitive as per https://vastvalidator.iabtechlab.com/dash) + {name: " all caps", want: want{err: errors.New("Invalid Duration")}, args: args{creativeTag: `0:0:10`}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc := etree.NewDocument() + doc.ReadFromString(tt.args.creativeTag) + dur, err := getDuration(doc.FindElement("./Creative")) + assert.Equal(t, tt.want.duration, dur) + assert.Equal(t, tt.want.err, err) + // if error expects 0 value for duration + if nil != err { + assert.Equal(t, 0, dur) + } + }) + } +} + +func BenchmarkGetDuration(b *testing.B) { + doc := etree.NewDocument() + doc.ReadFromString(` 0:0:56.3 `) + creative := doc.FindElement("/Creative") + for n := 0; n < b.N; n++ { + getDuration(creative) + } +} + +func TestGetCreativeId(t *testing.T) { + type args struct { + creativeTag string // ad element + } + type want struct { + id string + } + tests := []struct { + name string + args args + want want + }{ + {name: "creative tag with id", want: want{id: "233ff44"}, args: args{creativeTag: ``}}, + {name: "creative tag without id", want: want{id: ""}, args: args{creativeTag: ``}}, + {name: "no creative tag", want: want{id: ""}, args: args{creativeTag: ""}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc := etree.NewDocument() + doc.ReadFromString(tt.args.creativeTag) + id := getCreativeID(doc.FindElement("./Creative")) + assert.Equal(t, tt.want.id, id) + }) + } +} + +func BenchmarkGetCreativeID(b *testing.B) { + doc := etree.NewDocument() + doc.ReadFromString(` `) + creative := doc.FindElement("/Creative") + for n := 0; n < b.N; n++ { + getCreativeID(creative) + } +} + +func TestGetAdvertisers(t *testing.T) { + tt := []struct { + name string + vastStr string + expected []string + }{ + { + name: "vast_4_with_advertiser", + vastStr: ` + + + www.iabtechlab.com + + + `, + expected: []string{"www.iabtechlab.com"}, + }, + { + name: "vast_4_without_advertiser", + vastStr: ` + + + + + `, + expected: []string{}, + }, + { + name: "vast_4_with_empty_advertiser", + vastStr: ` + + + + + + `, + expected: []string{}, + }, + { + name: "vast_2_with_single_advertiser", + vastStr: ` + + + + + google.com + + + + + `, + expected: []string{"google.com"}, + }, + { + name: "vast_2_with_two_advertiser", + vastStr: ` + + + + + google.com + + + facebook.com + + + + + `, + expected: []string{"google.com", "facebook.com"}, + }, + { + name: "vast_2_with_no_advertiser", + vastStr: ` + + + + + `, + expected: []string{}, + }, + { + name: "vast_2_with_epmty_advertiser", + vastStr: ` + + + + + + + + + + `, + expected: []string{}, + }, + { + name: "vast_3_with_single_advertiser", + vastStr: ` + + + + + google.com + + + + + `, + expected: []string{"google.com"}, + }, + { + name: "vast_3_with_two_advertiser", + vastStr: ` + + + + + google.com + + + facebook.com + + + + + `, + expected: []string{"google.com", "facebook.com"}, + }, + { + name: "vast_3_with_no_advertiser", + vastStr: ` + + + + + `, + expected: []string{}, + }, + { + name: "vast_3_with_epmty_advertiser", + vastStr: ` + + + + + + + + + + `, + expected: []string{}, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + + doc := etree.NewDocument() + if err := doc.ReadFromString(tc.vastStr); err != nil { + t.Errorf("Failed to create etree doc from string %+v", err) + } + + vastDoc := doc.FindElement("./VAST") + vastVer := vastDoc.SelectAttrValue(`version`, `2.0`) + + ad := getAdElement(vastDoc) + + result := getAdvertisers(vastVer, ad) + + sort.Strings(result) + sort.Strings(tc.expected) + + if !assert.Equal(t, len(tc.expected), len(result), fmt.Sprintf("Expected slice length - %+v \nResult slice length - %+v", len(tc.expected), len(result))) { + return + } + + for i, expected := range tc.expected { + assert.Equal(t, expected, result[i], fmt.Sprintf("Element mismatch at position %d.\nExpected - %s\nActual - %s", i, expected, result[i])) + } + }) + } +} diff --git a/analytics/build/config_ow.go b/analytics/build/config_ow.go new file mode 100644 index 00000000000..faf43d3d370 --- /dev/null +++ b/analytics/build/config_ow.go @@ -0,0 +1,20 @@ +package build + +import ( + "fmt" + + "github.com/prebid/prebid-server/v2/analytics" +) + +// EnableAnalyticsModule will add the new module into the list of enabled analytics modules +var EnableAnalyticsModule = func(module analytics.Module, moduleList analytics.Runner) (analytics.Runner, error) { + if module == nil { + return nil, fmt.Errorf("module to be added is nil") + } + enabledModuleList, ok := moduleList.(enabledAnalytics) + if !ok { + return nil, fmt.Errorf("failed to convert moduleList interface from analytics.Module to analytics.enabledAnalytics") + } + enabledModuleList["pubstack"] = module + return enabledModuleList, nil +} diff --git a/analytics/build/config_ow_test.go b/analytics/build/config_ow_test.go new file mode 100644 index 00000000000..c85328c9436 --- /dev/null +++ b/analytics/build/config_ow_test.go @@ -0,0 +1,67 @@ +package build + +import ( + "errors" + "testing" + + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/analytics/filesystem" + "github.com/stretchr/testify/assert" +) + +func TestEnableAnalyticsModule(t *testing.T) { + + modules := enabledAnalytics{} + file, err := filesystem.NewFileLogger("xyz1.txt") + if err != nil { + t.Errorf("NewFileLogger returned error - %v", err.Error()) + } + + type arg struct { + moduleList analytics.Runner + module analytics.Module + } + + type want struct { + len int + error error + } + + tests := []struct { + description string + args arg + wants want + }{ + { + description: "add non-nil module to nil module-list", + args: arg{moduleList: nil, module: file}, + wants: want{len: 0, error: errors.New("failed to convert moduleList interface from analytics.Module to analytics.enabledAnalytics")}, + }, + { + description: "add nil module to non-nil module-list", + args: arg{moduleList: modules, module: nil}, + wants: want{len: 0, error: errors.New("module to be added is nil")}, + }, + { + description: "add non-nil module to non-nil module-list", + args: arg{moduleList: modules, module: file}, + wants: want{len: 1, error: nil}, + }, + } + + for _, tt := range tests { + actual, err := EnableAnalyticsModule(tt.args.module, tt.args.moduleList) + assert.Equal(t, err, tt.wants.error) + + if err == nil { + list, ok := actual.(enabledAnalytics) + if !ok { + t.Errorf("Failed to convert interface to enabledAnalytics for test case - [%v]", tt.description) + } + + if len(list) != tt.wants.len { + t.Errorf("length of enabled modules mismatched, expected - [%d] , got - [%d]", tt.wants.len, len(list)) + } + } + } +} diff --git a/analytics/build/xyz1.txt-20231123 b/analytics/build/xyz1.txt-20231123 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/analytics/build/xyz1.txt-20231124 b/analytics/build/xyz1.txt-20231124 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/analytics/build/xyz1.txt-20231127 b/analytics/build/xyz1.txt-20231127 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/analytics/pubmatic/helper.go b/analytics/pubmatic/helper.go new file mode 100644 index 00000000000..01a94bea9e4 --- /dev/null +++ b/analytics/pubmatic/helper.go @@ -0,0 +1,74 @@ +package pubmatic + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/analytics/pubmatic/mhttp" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +// PrepareLoggerURL returns the url for OW logger call +func PrepareLoggerURL(wlog *WloggerRecord, loggerURL string, gdprEnabled int) string { + if wlog == nil { + return "" + } + v := url.Values{} + + jsonString, err := json.Marshal(wlog.record) + if err != nil { + return "" + } + + v.Set(models.WLJSON, string(jsonString)) + v.Set(models.WLPUBID, strconv.Itoa(wlog.PubID)) + if gdprEnabled == 1 { + v.Set(models.WLGDPR, strconv.Itoa(gdprEnabled)) + } + queryString := v.Encode() + + finalLoggerURL := loggerURL + "?" + queryString + return finalLoggerURL +} + +// getGdprEnabledFlag returns gdpr flag set in the partner config +func getGdprEnabledFlag(partnerConfigMap map[int]map[string]string) int { + gdpr := 0 + if val := partnerConfigMap[models.VersionLevelConfigID][models.GDPR_ENABLED]; val != "" { + gdpr, _ = strconv.Atoi(val) + } + return gdpr +} + +// send function will send the owlogger to analytics endpoint +func send(rCtx *models.RequestCtx, url string, headers http.Header, mhc mhttp.MultiHttpContextInterface) { + startTime := time.Now() + hc, _ := mhttp.NewHttpCall(url, "") + + for k, v := range headers { + if len(v) != 0 { + hc.AddHeader(k, v[0]) + } + } + + if rCtx.KADUSERCookie != nil { + hc.AddCookie(models.KADUSERCOOKIE, rCtx.KADUSERCookie.Value) + } + + mhc.AddHttpCall(hc) + _, erc := mhc.Execute() + if erc != 0 { + glog.Errorf("Failed to send the owlogger for pub:[%d], profile:[%d], version:[%d].", + rCtx.PubID, rCtx.ProfileID, rCtx.VersionID) + + // we will not record at version level in prometheus metric + rCtx.MetricsEngine.RecordPublisherWrapperLoggerFailure(rCtx.PubIDStr, rCtx.ProfileIDStr, "") + return + } + rCtx.MetricsEngine.RecordSendLoggerDataTime(rCtx.Endpoint, rCtx.ProfileIDStr, time.Since(startTime)) + // TODO: this will increment HB specific metric (ow_pbs_sshb_*), verify labels +} diff --git a/analytics/pubmatic/helper_test.go b/analytics/pubmatic/helper_test.go new file mode 100644 index 00000000000..7608735046e --- /dev/null +++ b/analytics/pubmatic/helper_test.go @@ -0,0 +1,220 @@ +package pubmatic + +import ( + "net/http" + "net/url" + "testing" + + "github.com/golang/mock/gomock" + "github.com/prebid/prebid-server/v2/analytics/pubmatic/mhttp" + mock_mhttp "github.com/prebid/prebid-server/v2/analytics/pubmatic/mhttp/mock" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/stretchr/testify/assert" +) + +func TestPrepareLoggerURL(t *testing.T) { + type args struct { + wlog *WloggerRecord + loggerURL string + gdprEnabled int + } + tests := []struct { + name string + args args + owlogger string + }{ + { + name: "nil_wlog", + args: args{ + wlog: nil, + loggerURL: "http://t.pubmatic.com/wl", + gdprEnabled: 1, + }, + owlogger: "", + }, + { + name: "gdprEnabled=1", + args: args{ + wlog: &WloggerRecord{ + record: record{ + PubID: 10, + ProfileID: "1", + VersionID: "0", + }, + }, + loggerURL: "http://t.pubmatic.com/wl", + gdprEnabled: 1, + }, + owlogger: `http://t.pubmatic.com/wl?gdEn=1&json={"pubid":10,"pid":"1","pdvid":"0","dvc":{},"ft":0}&pubid=10`, + }, + { + name: "gdprEnabled=0", + args: args{ + wlog: &WloggerRecord{ + record: record{ + PubID: 10, + ProfileID: "1", + VersionID: "0", + }, + }, + loggerURL: "http://t.pubmatic.com/wl", + gdprEnabled: 0, + }, + owlogger: `http://t.pubmatic.com/wl?json={"pubid":10,"pid":"1","pdvid":"0","dvc":{},"ft":0}&pubid=10`, + }, + { + name: "private endpoint", + args: args{ + wlog: &WloggerRecord{ + record: record{ + PubID: 5, + ProfileID: "5", + VersionID: "1", + }, + }, + loggerURL: "http://10.172.141.11/wl", + gdprEnabled: 0, + }, + owlogger: `http://10.172.141.11/wl?json={"pubid":5,"pid":"5","pdvid":"1","dvc":{},"ft":0}&pubid=5`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + owlogger := PrepareLoggerURL(tt.args.wlog, tt.args.loggerURL, tt.args.gdprEnabled) + decodedOwlogger, _ := url.QueryUnescape(owlogger) + assert.Equal(t, tt.owlogger, decodedOwlogger, tt.name) + }) + } +} +func TestGetGdprEnabledFlag(t *testing.T) { + tests := []struct { + name string + partnerConfig map[int]map[string]string + gdprFlag int + }{ + { + name: "Empty partnerConfig", + partnerConfig: make(map[int]map[string]string), + gdprFlag: 0, + }, + { + name: "partnerConfig without versionlevel cfg", + partnerConfig: map[int]map[string]string{ + 2: {models.GDPR_ENABLED: "1"}, + }, + gdprFlag: 0, + }, + { + name: "partnerConfig without GDPR_ENABLED", + partnerConfig: map[int]map[string]string{ + models.VersionLevelConfigID: {"any": "1"}, + }, + gdprFlag: 0, + }, + { + name: "partnerConfig with invalid GDPR_ENABLED", + partnerConfig: map[int]map[string]string{ + models.VersionLevelConfigID: {models.GDPR_ENABLED: "non-int"}, + }, + gdprFlag: 0, + }, + { + name: "partnerConfig with GDPR_ENABLED=1", + partnerConfig: map[int]map[string]string{ + models.VersionLevelConfigID: {models.GDPR_ENABLED: "1"}, + }, + gdprFlag: 1, + }, + { + name: "partnerConfig with GDPR_ENABLED=2", + partnerConfig: map[int]map[string]string{ + models.VersionLevelConfigID: {models.GDPR_ENABLED: "2"}, + }, + gdprFlag: 2, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gdprFlag := getGdprEnabledFlag(tt.partnerConfig) + assert.Equal(t, tt.gdprFlag, gdprFlag, tt.name) + }) + } +} +func TestSendMethod(t *testing.T) { + // initialise global variables + mhttp.Init(1, 1, 1, 2000) + // init mock + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + type args struct { + rctx *models.RequestCtx + url string + headers http.Header + } + tests := []struct { + name string + args args + getMetricsEngine func() *mock_metrics.MockMetricsEngine + getMockMultiHttpContext func() *mock_mhttp.MockMultiHttpContextInterface + }{ + { + name: "send success", + args: args{ + rctx: &models.RequestCtx{ + PubIDStr: "5890", + ProfileIDStr: "1", + Endpoint: models.EndpointV25, + }, + url: "http://10.172.11.11/wl", + headers: http.Header{ + "key": []string{"val"}, + }, + }, + getMetricsEngine: func() *mock_metrics.MockMetricsEngine { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordSendLoggerDataTime(models.EndpointV25, "1", gomock.Any()) + return mockEngine + }, + getMockMultiHttpContext: func() *mock_mhttp.MockMultiHttpContextInterface { + mockHttpCtx := mock_mhttp.NewMockMultiHttpContextInterface(ctrl) + mockHttpCtx.EXPECT().AddHttpCall(gomock.Any()) + mockHttpCtx.EXPECT().Execute().Return(0, 0) + return mockHttpCtx + }, + }, + { + name: "send fail", + args: args{ + rctx: &models.RequestCtx{ + PubIDStr: "5890", + ProfileIDStr: "1", + Endpoint: models.EndpointV25, + KADUSERCookie: &http.Cookie{}, + }, + url: "http://10.172.11.11/wl", + headers: http.Header{ + "key": []string{"val"}, + }, + }, + getMetricsEngine: func() *mock_metrics.MockMetricsEngine { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPublisherWrapperLoggerFailure("5890", "1", "") + return mockEngine + }, + getMockMultiHttpContext: func() *mock_mhttp.MockMultiHttpContextInterface { + mockHttpCtx := mock_mhttp.NewMockMultiHttpContextInterface(ctrl) + mockHttpCtx.EXPECT().AddHttpCall(gomock.Any()) + mockHttpCtx.EXPECT().Execute().Return(0, 1) + return mockHttpCtx + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.args.rctx.MetricsEngine = tt.getMetricsEngine() + send(tt.args.rctx, tt.args.url, tt.args.headers, tt.getMockMultiHttpContext()) + }) + } +} diff --git a/analytics/pubmatic/logger.go b/analytics/pubmatic/logger.go new file mode 100644 index 00000000000..eb17defe125 --- /dev/null +++ b/analytics/pubmatic/logger.go @@ -0,0 +1,500 @@ +package pubmatic + +import ( + "encoding/json" + "fmt" + "strings" + + "net/http" + "strconv" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" + + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/utils" + "github.com/prebid/prebid-server/v2/openrtb_ext" + uuid "github.com/satori/go.uuid" +) + +type bidWrapper struct { + *openrtb2.Bid + Nbr *openrtb3.NonBidStatusCode +} + +// getUUID is a function variable which will return uuid +var getUUID = func() string { + return uuid.NewV4().String() +} + +// GetLogAuctionObjectAsURL will form the owlogger-url and http-headers +func GetLogAuctionObjectAsURL(ao analytics.AuctionObject, rCtx *models.RequestCtx, logInfo, forRespExt bool) (string, http.Header) { + if ao.RequestWrapper == nil || ao.RequestWrapper.BidRequest == nil || rCtx == nil || rCtx.PubID == 0 { + return "", nil + } + + wlog := WloggerRecord{ + record: record{ + PubID: rCtx.PubID, + ProfileID: fmt.Sprintf("%d", rCtx.ProfileID), + VersionID: fmt.Sprintf("%d", rCtx.DisplayVersionID), + Origin: rCtx.Origin, + PageURL: rCtx.PageURL, + IID: rCtx.LoggerImpressionID, + Timestamp: rCtx.StartTime, + ServerLogger: 1, + TestConfigApplied: rCtx.ABTestConfigApplied, + Timeout: int(rCtx.TMax), + PDC: rCtx.DCName, + CachePutMiss: rCtx.CachePutMiss, + }, + } + + wlog.logIntegrationType(rCtx.Endpoint) + wlog.logDeviceObject(rCtx, ao.RequestWrapper.BidRequest) + + if ao.RequestWrapper.User != nil { + extUser := openrtb_ext.ExtUser{} + _ = json.Unmarshal(ao.RequestWrapper.User.Ext, &extUser) + wlog.ConsentString = extUser.Consent + } + + if ao.RequestWrapper.Regs != nil { + extReg := openrtb_ext.ExtRegs{} + _ = json.Unmarshal(ao.RequestWrapper.Regs.Ext, &extReg) + if extReg.GDPR != nil { + wlog.GDPR = *extReg.GDPR + } + } + + if ao.RequestWrapper.Site != nil { + wlog.logContentObject(ao.RequestWrapper.Site.Content) + } else if ao.RequestWrapper.App != nil { + wlog.logContentObject(ao.RequestWrapper.App.Content) + } + + var ipr map[string][]PartnerRecord + if logInfo { + ipr = getDefaultPartnerRecordsByImp(rCtx) + } else { + ipr = getPartnerRecordsByImp(ao, rCtx) + } + + // parent bidder could in one of the above and we need them by prebid's bidderCode and not seat(could be alias) + slots := make([]SlotRecord, 0) + for _, imp := range ao.RequestWrapper.Imp { + impCtx, ok := rCtx.ImpBidCtx[imp.ID] + if !ok { + continue + } + reward := 0 + if impCtx.IsRewardInventory != nil { + reward = int(*impCtx.IsRewardInventory) + } + + // to keep existing response intact + partnerData := make([]PartnerRecord, 0) + if ipr[imp.ID] != nil { + partnerData = ipr[imp.ID] + } + + slots = append(slots, SlotRecord{ + SlotId: getUUID(), + SlotName: impCtx.SlotName, + SlotSize: impCtx.IncomingSlots, + Adunit: impCtx.AdUnitName, + PartnerData: partnerData, + RewardedInventory: int(reward), + // AdPodSlot: getAdPodSlot(imp, responseMap.AdPodBidsExt), + }) + } + + wlog.Slots = slots + + headers := http.Header{ + models.USER_AGENT_HEADER: []string{rCtx.UA}, + models.IP_HEADER: []string{rCtx.IP}, + } + + // first set the floor type from bidrequest.ext + if rCtx.NewReqExt != nil { + wlog.logFloorType(&rCtx.NewReqExt.Prebid) + } + + // set the floor details from tracker + floorDetailsSet := false + for _, tracker := range rCtx.Trackers { + wlog.FloorType = tracker.Tracker.FloorType + wlog.FloorModelVersion = tracker.Tracker.FloorModelVersion + wlog.FloorSource = tracker.Tracker.FloorSource + wlog.FloorFetchStatus = tracker.Tracker.LoggerData.FloorFetchStatus + wlog.FloorProvider = tracker.Tracker.LoggerData.FloorProvider + for i := range wlog.Slots { + wlog.Slots[i].FloorSkippedFlag = tracker.Tracker.FloorSkippedFlag + } + floorDetailsSet = true + break // For all trackers, floor-details are common so break the loop + } + + // if floor details not present in tracker then use response.ext + // this wil happen only if no valid bid is present in response.seatbid + if !floorDetailsSet { + if rCtx.ResponseExt.Prebid != nil { + // wlog.SetFloorDetails(rCtx.ResponseExt.Prebid.Floors) + floorDetails := models.GetFloorsDetails(rCtx.ResponseExt) + wlog.FloorSource = floorDetails.FloorSource + wlog.FloorModelVersion = floorDetails.FloorModelVersion + wlog.FloorFetchStatus = floorDetails.FloorFetchStatus + wlog.FloorProvider = floorDetails.FloorProvider + wlog.FloorType = floorDetails.FloorType + for i := range wlog.Slots { + wlog.Slots[i].FloorSkippedFlag = floorDetails.Skipfloors + } + } + } + + url := ow.cfg.Endpoint + if logInfo { + url = ow.cfg.PublicEndpoint + } + + return PrepareLoggerURL(&wlog, url, getGdprEnabledFlag(rCtx.PartnerConfigMap)), headers +} + +// TODO filter by name. (*stageOutcomes[8].Groups[0].InvocationResults[0].AnalyticsTags.Activities[0].Results[0].Values["request-ctx"].(data)) +func GetRequestCtx(hookExecutionOutcome []hookexecution.StageOutcome) *models.RequestCtx { + for _, stageOutcome := range hookExecutionOutcome { + for _, groups := range stageOutcome.Groups { + for _, invocationResult := range groups.InvocationResults { + for _, activity := range invocationResult.AnalyticsTags.Activities { + for _, result := range activity.Results { + if result.Values != nil { + if irctx, ok := result.Values["request-ctx"]; ok { + rctx, ok := irctx.(*models.RequestCtx) + if !ok { + return nil + } + return rctx + } + } + } + } + } + } + } + return nil +} + +func convertNonBidToBidWrapper(nonBid *openrtb_ext.NonBid) (bid bidWrapper) { + bid.Bid = &openrtb2.Bid{ + Price: nonBid.Ext.Prebid.Bid.Price, + ADomain: nonBid.Ext.Prebid.Bid.ADomain, + CatTax: nonBid.Ext.Prebid.Bid.CatTax, + Cat: nonBid.Ext.Prebid.Bid.Cat, + DealID: nonBid.Ext.Prebid.Bid.DealID, + W: nonBid.Ext.Prebid.Bid.W, + H: nonBid.Ext.Prebid.Bid.H, + Dur: nonBid.Ext.Prebid.Bid.Dur, + MType: nonBid.Ext.Prebid.Bid.MType, + ID: nonBid.Ext.Prebid.Bid.ID, + ImpID: nonBid.ImpId, + } + bidExt := models.BidExt{ + OriginalBidCPM: nonBid.Ext.Prebid.Bid.OriginalBidCPM, + OriginalBidCPMUSD: nonBid.Ext.Prebid.Bid.OriginalBidCPMUSD, + OriginalBidCur: nonBid.Ext.Prebid.Bid.OriginalBidCur, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealPriority: nonBid.Ext.Prebid.Bid.DealPriority, + DealTierSatisfied: nonBid.Ext.Prebid.Bid.DealTierSatisfied, + Meta: nonBid.Ext.Prebid.Bid.Meta, + Targeting: nonBid.Ext.Prebid.Bid.Targeting, + Type: nonBid.Ext.Prebid.Bid.Type, + Video: nonBid.Ext.Prebid.Bid.Video, + BidId: nonBid.Ext.Prebid.Bid.BidId, + Floors: nonBid.Ext.Prebid.Bid.Floors, + }, + }, + } + bidExtBytes, err := json.Marshal(bidExt) + if err == nil { + bid.Ext = bidExtBytes + } + // the 'nbr' field will be lost due to json.Marshal hence do not set it inside bid.Ext + // set the 'nbr' code at bid level, while forming partner-records we give high priority to bid.nbr over bid.ext.nbr + bid.Nbr = openwrap.GetNonBidStatusCodePtr(openrtb3.NonBidStatusCode(nonBid.StatusCode)) + return bid +} + +// getPartnerRecordsByImp creates partnerRecords of valid-bids + dropped-bids + non-bids+ default-bids for owlogger +func getPartnerRecordsByImp(ao analytics.AuctionObject, rCtx *models.RequestCtx) map[string][]PartnerRecord { + // impID-partnerRecords: partner records per impression + ipr := make(map[string][]PartnerRecord) + + rejectedBids := map[string]map[string]struct{}{} + loggerSeat := make(map[string][]bidWrapper) + + // currently, ao.SeatNonBid will contain the bids that got rejected in the prebid core + // it does not contain the bids that got rejected inside pubmatic ow module + // As of now, pubmatic ow module logs slot-not-map and partner-throttle related nonbids + // for which we don't create partner-record in the owlogger + for _, seatNonBid := range ao.SeatNonBid { + if _, ok := rejectedBids[seatNonBid.Seat]; !ok { + rejectedBids[seatNonBid.Seat] = map[string]struct{}{} + } + for _, nonBid := range seatNonBid.NonBid { + rejectedBids[seatNonBid.Seat][nonBid.ImpId] = struct{}{} + loggerSeat[seatNonBid.Seat] = append(loggerSeat[seatNonBid.Seat], convertNonBidToBidWrapper(&nonBid)) + } + } + + // SeatBid contains valid-bids + default/proxy bids + // loggerSeat should not contain duplicate entry for same imp-seat combination + for seatIndex, seatBid := range ao.Response.SeatBid { + for bidIndex, bid := range seatBid.Bid { + // Check if this is a default as well as nonbid. + // Ex. if only one bid is returned by pubmatic and it got rejected due to floors. + // then OW-Module will add one default/proxy bid in seatbid. + // and prebid core will add one nonbid in seat-non-bid. + // So, we want to skip this default/proxy bid to avoid duplicate entry in logger + if models.IsDefaultBid(&bid) { + if _, ok := rejectedBids[seatBid.Seat]; ok { + if _, ok := rejectedBids[seatBid.Seat][bid.ImpID]; ok { + continue + } + } + } + loggerSeat[seatBid.Seat] = append(loggerSeat[seatBid.Seat], bidWrapper{Bid: &ao.Response.SeatBid[seatIndex].Bid[bidIndex]}) + } + } + + // include bids that got dropped from ao.SeatBid by pubmatic ow module. Ex. sendAllBids=false + // in future, this dropped bids should be part of ao.SeatNonBid object + for seat, Bids := range rCtx.DroppedBids { + for bid := range Bids { + loggerSeat[seat] = append(loggerSeat[seat], bidWrapper{Bid: &rCtx.DroppedBids[seat][bid]}) + } + } + + // pubmatic's KGP details per impression + // This is only required for groupm bids (groupm bids log pubmatic's data in ow logger) + type pubmaticMarketplaceMeta struct { + PubmaticKGP, PubmaticKGPV, PubmaticKGPSV string + } + pmMkt := make(map[string]pubmaticMarketplaceMeta) + + // loggerSeat contains valid-bids + non-bids + dropped-bids + default-bids (ex-partnerTimeout) + for seat, bids := range loggerSeat { + if seat == string(openrtb_ext.BidderOWPrebidCTV) { + continue + } + + // owlogger would not contain throttled bidders, do we need this + if _, ok := rCtx.AdapterThrottleMap[seat]; ok { + continue + } + + for _, bid := range bids { + impCtx, ok := rCtx.ImpBidCtx[bid.ImpID] + if !ok { + continue + } + + // owlogger would not contain non-mapped bidders, do we need this + if _, ok := impCtx.NonMapped[seat]; ok { + break + } + + var kgp, kgpv, kgpsv, adFormat string + + revShare := 0.0 + partnerID := seat + bidderMeta, ok := impCtx.Bidders[seat] + if ok { + partnerID = bidderMeta.PrebidBidderCode + kgp = bidderMeta.KGP + revShare, _ = strconv.ParseFloat(rCtx.PartnerConfigMap[bidderMeta.PartnerID][models.REVSHARE], 64) + } + + // impBidCtx contains info about valid-bids + dropped-bids + default-bids + // impBidCtx not contains info about seat-non-bids. + // impBidCtx contains bid-id in form of 'bid-id::uuid' for valid-bids + dropped-bids + // impBidCtx contains bid-id in form of 'uuid' for default-bids + var bidExt models.BidExt + json.Unmarshal(bid.Ext, &bidExt) + + bidIDForLookup := bid.ID + if bidExt.Prebid != nil && !strings.Contains(bid.ID, models.BidIdSeparator) { + // this block will not be executed for default-bids. + bidIDForLookup = utils.SetUniqueBidID(bid.ID, bidExt.Prebid.BidId) + } + + bidCtx, ok := impCtx.BidCtx[bidIDForLookup] + if ok { + // override bidExt for valid-bids + default-bids + dropped-bids + // since we have already prepared it under auction-response-hook + bidExt = bidCtx.BidExt + } + + // get the tracker details from rctx, to avoid repetitive computation. + // tracker will be available only for valid-bids and will be absent for dropped-bids + default-bids + seat-non-bids + // tracker contains bid-id in form of 'bid-id::uuid' + tracker, trackerPresent := rCtx.Trackers[bidIDForLookup] + + adFormat = tracker.Tracker.PartnerInfo.Adformat + if adFormat == "" { + adFormat = models.GetAdFormat(bid.Bid, &bidExt, &impCtx) + } + + kgpv = tracker.Tracker.PartnerInfo.KGPV + kgpsv = tracker.Tracker.LoggerData.KGPSV + if kgpv == "" || kgpsv == "" { + kgpv, kgpsv = models.GetKGPSV(*bid.Bid, bidderMeta, adFormat, impCtx.TagID, impCtx.Div, rCtx.Source) + } + + price := bid.Price + if ao.Response.Cur != models.USD { + if bidCtx.EG != 0 { // valid-bids + dropped-bids+ default-bids + price = bidCtx.EG + } else if bidExt.OriginalBidCPMUSD != 0 { // valid non-bids + price = bidExt.OriginalBidCPMUSD + } + } + + if seat == models.BidderPubMatic { + pmMkt[bid.ImpID] = pubmaticMarketplaceMeta{ + PubmaticKGP: kgp, + PubmaticKGPV: kgpv, + PubmaticKGPSV: kgpsv, + } + } + + nbr := bid.Nbr // only for seat-non-bids this will present at bid level + if nbr == nil { + nbr = bidExt.Nbr // valid-bids + default-bids + dropped-bids + } + + pr := PartnerRecord{ + PartnerID: partnerID, // prebid biddercode + BidderCode: seat, // pubmatic biddercode: pubmatic2 + Latency1: rCtx.BidderResponseTimeMillis[seat], // it is set inside auctionresponsehook for all bidders + KGPV: kgpv, + KGPSV: kgpsv, + BidID: utils.GetOriginalBidId(bid.ID), + OrigBidID: utils.GetOriginalBidId(bid.ID), + DefaultBidStatus: 0, // this will be always 0 , decide whether to drop this field in future + ServerSide: 1, + MatchedImpression: rCtx.MatchedImpression[seat], + OriginalCPM: models.GetGrossEcpm(bidExt.OriginalBidCPM), + OriginalCur: bidExt.OriginalBidCur, + DealID: bid.DealID, + Nbr: nbr, + Adformat: adFormat, + NetECPM: tracker.Tracker.PartnerInfo.NetECPM, + GrossECPM: tracker.Tracker.PartnerInfo.GrossECPM, + PartnerSize: tracker.Tracker.PartnerInfo.AdSize, + ADomain: tracker.Tracker.PartnerInfo.Advertiser, + } + + if pr.NetECPM == 0 { + pr.NetECPM = models.GetNetEcpm(price, revShare) + } + + if pr.GrossECPM == 0 { + pr.GrossECPM = models.GetGrossEcpm(price) + } + + if pr.PartnerSize == "" { + pr.PartnerSize = models.GetSizeForPlatform(bid.W, bid.H, rCtx.Platform) + } + + if trackerPresent { + pr.FloorRuleValue = tracker.Tracker.PartnerInfo.FloorRuleValue + pr.FloorValue = tracker.Tracker.PartnerInfo.FloorValue + } else { + pr.FloorValue, pr.FloorRuleValue = models.GetBidLevelFloorsDetails(bidExt, impCtx, rCtx.CurrencyConversion) + } + + if nbr != nil && *nbr == openrtb3.NoBidTimeoutError { + pr.PostTimeoutBidStatus = 1 + pr.Latency1 = 0 + } + + // WinningBids contains map of imp.id against bid.id+::+uuid + if b, ok := rCtx.WinningBids[bid.ImpID]; ok && b.ID == bidIDForLookup { + pr.WinningBidStaus = 1 + } + + if len(pr.OriginalCur) == 0 { + pr.OriginalCPM = float64(0) + pr.OriginalCur = models.USD + } + + if len(pr.DealID) != 0 { + pr.DealChannel = models.DEFAULT_DEALCHANNEL + } else { + pr.DealID = models.DealIDAbsent + } + + if bidExt.Prebid != nil { + if bidExt.Prebid.DealTierSatisfied && bidExt.Prebid.DealPriority > 0 { + pr.DealPriority = bidExt.Prebid.DealPriority + } + + if bidExt.Prebid.Video != nil && bidExt.Prebid.Video.Duration > 0 { + pr.AdDuration = &bidExt.Prebid.Video.Duration + } + + if bidExt.Prebid.Meta != nil { + pr.setMetaDataObject(bidExt.Prebid.Meta) + } + + if len(bidExt.Prebid.BidId) > 0 { + pr.BidID = bidExt.Prebid.BidId + } + } + + if pr.ADomain == "" && len(bid.ADomain) != 0 { + if domain, err := models.ExtractDomain(bid.ADomain[0]); err == nil { + pr.ADomain = domain + } + } + + ipr[bid.ImpID] = append(ipr[bid.ImpID], pr) + } + } + + // overwrite marketplace bid details with that of partner adatper + if rCtx.MarketPlaceBidders != nil { + for impID, partnerRecords := range ipr { + for i := 0; i < len(partnerRecords); i++ { + if _, ok := rCtx.MarketPlaceBidders[partnerRecords[i].BidderCode]; ok { + partnerRecords[i].PartnerID = "pubmatic" + partnerRecords[i].KGPV = pmMkt[impID].PubmaticKGPV + partnerRecords[i].KGPSV = pmMkt[impID].PubmaticKGPSV + } + } + ipr[impID] = partnerRecords + } + } + + return ipr +} + +// getDefaultPartnerRecordsByImp creates partnerRecord with default placeholders if req.ext.wrapper.loginfo=true. +// in future, check if this can be deprecated +func getDefaultPartnerRecordsByImp(rCtx *models.RequestCtx) map[string][]PartnerRecord { + ipr := make(map[string][]PartnerRecord) + for impID := range rCtx.ImpBidCtx { + ipr[impID] = []PartnerRecord{{ + ServerSide: 1, + DefaultBidStatus: 1, + PartnerSize: "0x0", + DealID: "-1", + }} + } + return ipr +} diff --git a/analytics/pubmatic/logger_test.go b/analytics/pubmatic/logger_test.go new file mode 100644 index 00000000000..37f033a2bda --- /dev/null +++ b/analytics/pubmatic/logger_test.go @@ -0,0 +1,4367 @@ +package pubmatic + +import ( + "encoding/json" + "net/http" + "net/url" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestGetRequestCtx(t *testing.T) { + tests := []struct { + name string + hookExecutionOutcome []hookexecution.StageOutcome + rctx *models.RequestCtx + }{ + { + name: "rctx present", + hookExecutionOutcome: []hookexecution.StageOutcome{ + { + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{ + Activities: []hookanalytics.Activity{ + { + Results: []hookanalytics.Result{ + { + Values: map[string]interface{}{ + "request-ctx": &models.RequestCtx{}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + rctx: &models.RequestCtx{}, + }, + { + name: "rctx of invalid type", + hookExecutionOutcome: []hookexecution.StageOutcome{ + { + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{ + Activities: []hookanalytics.Activity{ + { + Results: []hookanalytics.Result{ + { + Values: map[string]interface{}{ + "request-ctx": []string{}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + rctx: nil, + }, + { + name: "rctx absent", + hookExecutionOutcome: []hookexecution.StageOutcome{ + { + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{}, + }, + }, + }, + }, + rctx: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rctx := GetRequestCtx(tt.hookExecutionOutcome) + assert.Equal(t, tt.rctx, rctx, tt.name) + }) + } +} +func TestConvertNonBidToBid(t *testing.T) { + tests := []struct { + name string + nonBid openrtb_ext.NonBid + bid bidWrapper + }{ + { + name: "nonbid to bidwrapper", + nonBid: openrtb_ext.NonBid{ + StatusCode: int(openrtb3.LossBidBelowAuctionFloor), + ImpId: "imp1", + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + ADomain: []string{"abc.com"}, + DealID: "d1", + OriginalBidCPM: 10, + OriginalBidCur: models.USD, + OriginalBidCPMUSD: 0, + W: 10, + H: 50, + DealPriority: 1, + Video: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 10, + }, + }, + }, + }, + }, + bid: bidWrapper{ + &openrtb2.Bid{ + ImpID: "imp1", + Price: 10, + ADomain: []string{"abc.com"}, + DealID: "d1", + W: 10, + H: 50, + Ext: json.RawMessage(`{"prebid":{"dealpriority":1,"video":{"duration":10,"primary_category":"","vasttagid":""}},"origbidcpm":10,"origbidcur":"USD"}`), + }, + openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidBelowAuctionFloor), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bid := convertNonBidToBidWrapper(&tt.nonBid) + assert.Equal(t, tt.bid, bid, tt.name) + }) + } +} +func TestGetDefaultPartnerRecordsByImp(t *testing.T) { + tests := []struct { + name string + rCtx *models.RequestCtx + partners map[string][]PartnerRecord + }{ + { + name: "empty ImpBidCtx", + rCtx: &models.RequestCtx{}, + partners: map[string][]PartnerRecord{}, + }, + { + name: "multiple imps", + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + "imp2": {}, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + PartnerRecord{ + ServerSide: 1, + DefaultBidStatus: 1, + PartnerSize: "0x0", + DealID: "-1", + }, + }, + "imp2": { + PartnerRecord{ + ServerSide: 1, + DefaultBidStatus: 1, + PartnerSize: "0x0", + DealID: "-1", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getDefaultPartnerRecordsByImp(tt.rCtx) + assert.Equal(t, len(tt.partners), len(partners), tt.name) + for ind := range partners { + // ignore order of elements in slice while comparison + assert.ElementsMatch(t, partners[ind], tt.partners[ind], tt.name) + } + }) + } +} +func TestGetPartnerRecordsByImp(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "adformat for default bid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 0, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + }, + }, + Video: &openrtb2.Video{ + MinDuration: 1, + MaxDuration: 10, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + Adformat: models.Video, + }, + }, + }, + }, + { + name: "adformat for valid bid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + AdM: "{\"native\":{\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"Lexus - Luxury vehicles company\"}},{\"id\":2,\"img\":{\"h\":150,\"url\":\"https://stagingnyc.pubmatic.com:8443//sdk/lexus_logo.png\",\"w\":150},\"required\":0},{\"id\":3,\"img\":{\"h\":428,\"url\":\"https://stagingnyc.pubmatic.com:8443//sdk/28f48244cafa0363b03899f267453fe7%20copy.png\",\"w\":214},\"required\":0},{\"data\":{\"value\":\"Goto PubMatic\"},\"id\":4,\"required\":0},{\"data\":{\"value\":\"Lexus - Luxury vehicles company\"},\"id\":5,\"required\":0},{\"data\":{\"value\":\"4\"},\"id\":6,\"required\":0}],\"imptrackers\":[\"http://phtrack.pubmatic.com/?ts=1496043362&r=84137f17-eefd-4f06-8380-09138dc616e6&i=c35b1240-a0b3-4708-afca-54be95283c61&a=130917&t=9756&au=10002949&p=&c=10014299&o=10002476&wl=10009731&ty=1\"],\"link\":{\"clicktrackers\":[\"http://ct.pubmatic.com/track?ts=1496043362&r=84137f17-eefd-4f06-8380-09138dc616e6&i=c35b1240-a0b3-4708-afca-54be95283c61&a=130917&t=9756&au=10002949&p=&c=10014299&o=10002476&wl=10009731&ty=3&url=\"],\"url\":\"http://www.lexus.com/\"},\"ver\":1}}", + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + }, + }, + Native: &openrtb2.Native{}, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + Adformat: models.Native, + NetECPM: 10, + GrossECPM: 10, + }, + }, + }, + }, + { + name: "latency for partner", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + AdM: "{\"native\":{\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"Lexus - Luxury vehicles company\"}},{\"id\":2,\"img\":{\"h\":150,\"url\":\"https://stagingnyc.pubmatic.com:8443//sdk/lexus_logo.png\",\"w\":150},\"required\":0},{\"id\":3,\"img\":{\"h\":428,\"url\":\"https://stagingnyc.pubmatic.com:8443//sdk/28f48244cafa0363b03899f267453fe7%20copy.png\",\"w\":214},\"required\":0},{\"data\":{\"value\":\"Goto PubMatic\"},\"id\":4,\"required\":0},{\"data\":{\"value\":\"Lexus - Luxury vehicles company\"},\"id\":5,\"required\":0},{\"data\":{\"value\":\"4\"},\"id\":6,\"required\":0}],\"imptrackers\":[\"http://phtrack.pubmatic.com/?ts=1496043362&r=84137f17-eefd-4f06-8380-09138dc616e6&i=c35b1240-a0b3-4708-afca-54be95283c61&a=130917&t=9756&au=10002949&p=&c=10014299&o=10002476&wl=10009731&ty=1\"],\"link\":{\"clicktrackers\":[\"http://ct.pubmatic.com/track?ts=1496043362&r=84137f17-eefd-4f06-8380-09138dc616e6&i=c35b1240-a0b3-4708-afca-54be95283c61&a=130917&t=9756&au=10002949&p=&c=10014299&o=10002476&wl=10009731&ty=3&url=\"],\"url\":\"http://www.lexus.com/\"},\"ver\":1}}", + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + }, + }, + Native: &openrtb2.Native{}, + }, + }, + BidderResponseTimeMillis: map[string]int{ + "pubmatic": 20, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + Adformat: models.Native, + NetECPM: 10, + GrossECPM: 10, + Latency1: 20, + }, + }, + }, + }, + { + name: "matchedimpression for partner", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + }, + }, + }, + }, + MatchedImpression: map[string]int{ + "pubmatic": 1, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + NetECPM: 10, + GrossECPM: 10, + MatchedImpression: 1, + }, + }, + }, + }, + { + name: "partnersize for non-video bid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + W: 30, + H: 50, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + }, + }, + }, + }, + Platform: models.PLATFORM_DISPLAY, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "30x50", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + NetECPM: 10, + GrossECPM: 10, + }, + }, + }, + }, + { + name: "partnersize for video bid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + W: 30, + H: 50, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + }, + }, + }, + }, + Platform: models.PLATFORM_VIDEO, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "30x50v", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + NetECPM: 10, + GrossECPM: 10, + }, + }, + }, + }, + { + name: "dealid present, verify dealid and dealchannel", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + DealID: "pubdeal", + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "pubdeal", + DealChannel: "PMP", + ServerSide: 1, + OriginalCur: "USD", + NetECPM: 10, + GrossECPM: 10, + }, + }, + }, + }, + { + name: "log adomain field", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + ADomain: []string{ + "http://google.com", "http://yahoo.com", + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + BidId: "prebid-bid-id-1", + }, + }, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "prebid-bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + ADomain: "google.com", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equalf(t, partners, tt.partners, tt.name) + }) + } +} +func TestGetPartnerRecordsByImpForTracker(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "prefer tracker details, avoid computation", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + Trackers: map[string]models.OWTracker{ + "bid-id-1": { + Tracker: models.Tracker{ + PartnerInfo: models.Partner{ + Adformat: models.Native, + KGPV: "kgpv", + NetECPM: 10, + GrossECPM: 12, + AdSize: "15x15", + FloorValue: 1, + FloorRuleValue: 2, + Advertiser: "sony.com", + }, + LoggerData: models.LoggerData{ + KGPSV: "kgpsv", + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "15x15", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + Adformat: models.Native, + NetECPM: 10, + GrossECPM: 12, + FloorValue: 1, + FloorRuleValue: 2, + ADomain: "sony.com", + KGPV: "kgpv", + KGPSV: "kgpsv", + }, + }, + }, + }, + { + name: "tracker absent, compute data", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + W: 15, + H: 15, + Price: 12, + ADomain: []string{"http://sony.com"}, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + Trackers: map[string]models.OWTracker{}, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Floors: &openrtb_ext.ExtBidPrebidFloors{ + FloorValue: 1, + FloorRuleValue: 2, + }, + Type: models.Native, + }, + }, + }, + }, + }, + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PrebidBidderCode: "pubmatic", + PartnerID: 1, + KGP: "kgp", + KGPV: "kgpv", + }, + }, + Native: &openrtb2.Native{}, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "15x15", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + Adformat: models.Native, + NetECPM: 12, + GrossECPM: 12, + FloorValue: 1, + FloorRuleValue: 2, + ADomain: "sony.com", + KGPV: "kgpv", + KGPSV: "kgpv", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equalf(t, partners, tt.partners, tt.name) + }) + } +} +func TestGetPartnerRecordsByImpForDroppedBids(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "all bids got dropped", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + DroppedBids: map[string][]openrtb2.Bid{ + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + "appnexus": { + { + ID: "bid-id-2", + ImpID: "imp1", + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PartnerID: 1, + PrebidBidderCode: "pubmatic", + }, + "appnexus": { + PartnerID: 2, + PrebidBidderCode: "appnexus", + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + }, + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-2", + OrigBidID: "bid-id-2", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + }, + }, + }, + }, + { + name: "1 bid got dropped, 1 bid is present in seatbid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + DroppedBids: map[string][]openrtb2.Bid{ + "appnexus": { + { + ID: "bid-id-2", + ImpID: "imp1", + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PartnerID: 1, + PrebidBidderCode: "pubmatic", + }, + "appnexus": { + PartnerID: 2, + PrebidBidderCode: "appnexus", + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + }, + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-2", + OrigBidID: "bid-id-2", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equal(t, len(tt.partners), len(partners), tt.name) + for ind := range partners { + // ignore order of elements in slice while comparison + assert.ElementsMatch(t, partners[ind], tt.partners[ind], tt.name) + } + }) + } +} +func TestGetPartnerRecordsByImpForDefaultBids(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "no default bid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + NetECPM: 10, + GrossECPM: 10, + }, + }, + }, + }, + { + name: "partner timeout case, default bid present is seat-bid but absent in seat-non-bid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 0, + }, + }, + Seat: "pubmatic", + }, + }, + }, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(openrtb3.LossBidBelowAuctionFloor), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + ID: "bid-id-2", + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.NoBidTimeoutError), + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-2", + OrigBidID: "bid-id-2", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidBelowAuctionFloor), + }, + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + NetECPM: 0, + GrossECPM: 0, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.NoBidTimeoutError), + PostTimeoutBidStatus: 1, + }, + }, + }, + }, + { + name: "floor rejected bid, default bid present in seat-bid and same bid is available in seat-non-bid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 0, + }, + }, + Seat: "pubmatic", + }, + }, + }, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(openrtb3.LossBidBelowAuctionFloor), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + ID: "bid-id-1", + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.NoBidGeneralError), + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: "USD", + NetECPM: 0, + GrossECPM: 0, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidBelowAuctionFloor), + }, + }, + }, + }, + { + name: "slot not mapped, default bid present is seat-bid but absent in seat-non-bid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 0, + }, + }, + Seat: "pubmatic", + }, + }, + }, + SeatNonBid: []openrtb_ext.SeatNonBid{}, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.NoBidGeneralError), + }, + }, + }, + NonMapped: map[string]struct{}{ + "pubmatic": {}, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{}, + }, + { + name: "partner throttled, default bid present is seat-bid but absent in seat-non-bid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 0, + }, + }, + Seat: "pubmatic", + }, + }, + }, + SeatNonBid: []openrtb_ext.SeatNonBid{}, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.NoBidGeneralError), + }, + }, + }, + }, + }, + AdapterThrottleMap: map[string]struct{}{ + "pubmatic": {}, + }, + }, + }, + partners: map[string][]PartnerRecord{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equal(t, len(tt.partners), len(partners), tt.name) + for ind := range partners { + // ignore order of elements in slice while comparison + if !assert.ElementsMatch(t, partners[ind], tt.partners[ind], tt.name) { + assert.Equal(t, partners[ind], tt.partners[ind], tt.name) + } + } + }) + } +} +func TestGetPartnerRecordsByImpForSeatNonBid(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "empty seatnonbids, expect empty partnerRecord", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + SeatNonBid: []openrtb_ext.SeatNonBid{}, + }, + rCtx: &models.RequestCtx{}, + }, + partners: map[string][]PartnerRecord{}, + }, + { + name: "ImpBidCtx is must to log partner-record in logger", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(openrtb3.LossBidBelowDealFloor), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: make(map[string]models.ImpCtx), + }, + }, + partners: map[string][]PartnerRecord{}, + }, + { + name: "log rejected non-bid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(openrtb3.LossBidBelowAuctionFloor), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + ID: "bid-id-1", + W: 10, + H: 50, + OriginalBidCPM: 10, + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + Bidders: map[string]models.PartnerData{ + "appnexus": { + PartnerID: 1, + PrebidBidderCode: "appnexus", + KGP: "kgp", + KGPV: "kgpv", + }, + }, + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidBelowAuctionFloor), + }, + }, + }, + BidFloor: 10.5, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: { + "rev_share": "0", + }, + }, + WinningBids: make(map[string]models.OwBid), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + KGPV: "kgpv", + KGPSV: "kgpv", + PartnerSize: "10x50", + GrossECPM: 10, + NetECPM: 10, + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCPM: 0, + OriginalCur: models.USD, + FloorValue: 10.5, + FloorRuleValue: 10.5, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidBelowAuctionFloor), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equal(t, tt.partners, partners, tt.name) + }) + } +} +func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "bid.ext.prebid.floors has high priority than imp.bidfloor", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(openrtb3.LossBidBelowAuctionFloor), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + ID: "bid-id-1", + Floors: &openrtb_ext.ExtBidPrebidFloors{ + FloorRule: "*|*|ebay.com", + FloorRuleValue: 1, + FloorValue: 1, + FloorCurrency: models.USD, + }, + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidFloor: 10.5, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: {}, + }, + WinningBids: make(map[string]models.OwBid), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + GrossECPM: 10, + NetECPM: 10, + ServerSide: 1, + OriginalCPM: 0, + OriginalCur: models.USD, + FloorValue: 1, + FloorRuleValue: 1, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidBelowAuctionFloor), + }, + }, + }, + }, + { + name: "bid.ext.prebid.floors can have 0 value", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int((openrtb3.LossBidBelowAuctionFloor)), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + ID: "bid-id-1", + Floors: &openrtb_ext.ExtBidPrebidFloors{ + FloorRule: "*|*|ebay.com", + FloorRuleValue: 0, + FloorValue: 0, + FloorCurrency: models.USD, + }, + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidFloor: 10.5, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: {}, + }, + WinningBids: make(map[string]models.OwBid), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + GrossECPM: 10, + NetECPM: 10, + ServerSide: 1, + OriginalCPM: 0, + OriginalCur: models.USD, + FloorValue: 0, + FloorRuleValue: 0, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidBelowAuctionFloor), + }, + }, + }, + }, + { + name: "bid.ext.prebid.floors.floorRuleValue is 0 then set it to bid.ext.prebid.floors.floorRule", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int((openrtb3.LossBidBelowAuctionFloor)), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + ID: "bid-id-1", + Floors: &openrtb_ext.ExtBidPrebidFloors{ + FloorRule: "*|*|ebay.com", + FloorRuleValue: 0, + FloorValue: 10, + FloorCurrency: models.USD, + }, + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidFloor: 10.5, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: {}, + }, + WinningBids: make(map[string]models.OwBid), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + GrossECPM: 10, + NetECPM: 10, + ServerSide: 1, + OriginalCPM: 0, + OriginalCur: models.USD, + FloorValue: 10, + FloorRuleValue: 10, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidBelowAuctionFloor), + }, + }, + }, + }, + { + name: "bid.ext.prebid.floors not set, fallback to imp.bidfloor", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(openrtb3.LossBidBelowAuctionFloor), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + ID: "bid-id-1", + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidFloor: 10.567, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: {}, + }, + WinningBids: make(map[string]models.OwBid), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + GrossECPM: 10, + NetECPM: 10, + ServerSide: 1, + OriginalCPM: 0, + OriginalCur: models.USD, + FloorValue: 10.57, + FloorRuleValue: 10.57, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidBelowAuctionFloor), + }, + }, + }, + }, + { + name: "currency conversion when floor value is set to imp.bidfloor", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(openrtb3.LossBidBelowAuctionFloor), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + ID: "bid-id-1", + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + CurrencyConversion: func(from, to string, value float64) (float64, error) { + return 1000, nil + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidFloor: 10.567, + BidFloorCur: "JPY", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: {}, + }, + WinningBids: make(map[string]models.OwBid), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + GrossECPM: 10, + NetECPM: 10, + ServerSide: 1, + OriginalCPM: 0, + OriginalCur: models.USD, + FloorValue: 1000, + FloorRuleValue: 1000, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidBelowAuctionFloor), + }, + }, + }, + }, + { + name: "currency conversion when floor value is set from bid.ext.prebid.floors", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(openrtb3.LossBidBelowAuctionFloor), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + ID: "bid-id-1", + Floors: &openrtb_ext.ExtBidPrebidFloors{ + FloorRule: "*|*|ebay.com", + FloorRuleValue: 1, + FloorValue: 1, + FloorCurrency: "JPY", + }, + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + CurrencyConversion: func(from, to string, value float64) (float64, error) { + return 0.12, nil + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidFloor: 10.567, + BidFloorCur: "JPY", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: {}, + }, + WinningBids: make(map[string]models.OwBid), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + GrossECPM: 10, + NetECPM: 10, + ServerSide: 1, + OriginalCPM: 0, + OriginalCur: models.USD, + FloorValue: 0.12, + FloorRuleValue: 0.12, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidBelowAuctionFloor), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equal(t, tt.partners, partners, tt.name) + }) + } +} +func TestGetPartnerRecordsByImpForReserveredBidders(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "ignore prebid_ctv bidder", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "prebid_ctv", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{}, + }, + }, + partners: map[string][]PartnerRecord{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equal(t, tt.partners, partners, tt.name) + }) + } +} +func TestGetPartnerRecordsByImpForPostTimeoutBidStatus(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "update 't' when Partner Timed out", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.NoBidTimeoutError), + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + PostTimeoutBidStatus: 1, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.NoBidTimeoutError), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equal(t, tt.partners, partners, tt.name) + }) + } +} +func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "valid bid, impBidCtx bidID is in bidID::uuid format", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{"prebid":{"bidid":"uuid"}}`), + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1::uuid": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealPriority: 1, + DealTierSatisfied: true, + BidId: "uuid", + }, + }, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "uuid", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + NetECPM: 10, + GrossECPM: 10, + DealPriority: 1, + }, + }, + }, + }, + { + name: "valid bid, but json unmarshal fails", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{"prebid":{`), + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1::uuid": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealPriority: 1, + DealTierSatisfied: true, + BidId: "uuid", + }, + }, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + NetECPM: 10, + GrossECPM: 10, + DealPriority: 0, + }, + }, + }, + }, + { + name: "dropped bid, impBidCtx bidID is in bidID::uuid format", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{"prebid":{"bidid":"uuid"}}`), + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1::uuid": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealPriority: 1, + DealTierSatisfied: true, + }, + }, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + NetECPM: 10, + GrossECPM: 10, + DealPriority: 1, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + }, + }, + }, + }, + { + name: "default bid, impBidCtx bidID is in uuid format", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "uuid", + ImpID: "imp1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "uuid": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.NoBidTimeoutError), + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "uuid", + OrigBidID: "uuid", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.NoBidTimeoutError), + PostTimeoutBidStatus: 1, + }, + }, + }, + }, + { + name: "non bid, no bidCtx in impBidCtx", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(openrtb3.LossBidLostToDealBid), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + ID: "bid-id-1", + BidId: "uuid", + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "uuid", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + NetECPM: 10, + GrossECPM: 10, + OriginalCur: models.USD, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + }, + }, + }, + }, + { + name: "winning bid contains bidID in bidID::uuid format", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{"prebid":{"bidid":"uuid"}}`), + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1::uuid": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealPriority: 1, + DealTierSatisfied: true, + BidId: "uuid", + }, + }, + }, + }, + }, + }, + }, + WinningBids: map[string]models.OwBid{ + "imp1": { + ID: "bid-id-1::uuid", + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "uuid", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + NetECPM: 10, + GrossECPM: 10, + DealPriority: 1, + WinningBidStaus: 1, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equal(t, tt.partners, partners, tt.name) + }) + } +} +func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "valid bid, but bid.ext is empty", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{}`), + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1::uuid": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealPriority: 1, + DealTierSatisfied: true, + BidId: "uuid", + }, + }, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + NetECPM: 10, + GrossECPM: 10, + DealPriority: 0, + }, + }, + }, + }, + { + name: "dropped bid, bidExt unmarshal fails", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{"prebid":{`), + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1::uuid": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealPriority: 1, + DealTierSatisfied: true, + }, + }, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + NetECPM: 10, + GrossECPM: 10, + DealPriority: 0, + Nbr: nil, + }, + }, + }, + }, + { + name: "default bid, bidExt unmarshal fails", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "uuid", + ImpID: "imp1", + Ext: json.RawMessage(`{{{`), + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "uuid": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.NoBidTimeoutError), + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "uuid", + OrigBidID: "uuid", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.NoBidTimeoutError), + PostTimeoutBidStatus: 1, + }, + }, + }, + }, + { + name: "non bid, bidExt empty", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(openrtb3.LossBidLostToDealBid), + Ext: openrtb_ext.NonBidExt{}, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "", + OrigBidID: "", + DealID: "-1", + ServerSide: 1, + NetECPM: 0, + GrossECPM: 0, + OriginalCur: models.USD, + Nbr: openwrap.GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equal(t, tt.partners, partners, tt.name) + }) + } +} +func TestGetPartnerRecordsByImpForBidExtPrebidObject(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "log metadata object", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Meta: &openrtb_ext.ExtBidPrebidMeta{ + NetworkID: 100, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + MetaData: &MetaData{ + NetworkID: 100, + }, + }, + }, + }, + }, + { + name: "dealPriority is 1 but DealTierSatisfied is false", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + DealPriority: 1, + }, + }, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + }, + }, + }, + }, + { + name: "dealPriority is 1 and DealTierSatisfied is true", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + DealPriority: 1, + }, + }, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + DealPriority: 1, + }, + }, + }, + }, + { + name: "dealPriority is 0 and DealTierSatisfied is true", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + DealPriority: 0, + }, + }, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + DealPriority: 0, + }, + }, + }, + }, + { + name: "bidExt.Prebid.Video.Duration is 0 ", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Video: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 0, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + }, + }, + }, + }, + { + name: "bidExt.Prebid.Video.Duration is valid, log AdDuration", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Video: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 10, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + AdDuration: ptrutil.ToPtr(10), + }, + }, + }, + }, + { + name: "override bidid by bidExt.Prebid.bidID", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + BidId: "prebid-bid-id-1", + }, + }, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "prebid-bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equal(t, tt.partners, partners, tt.name) + }) + } +} +func TestGetPartnerRecordsByImpForRevShareAndBidCPM(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "origbidcpmusd not present", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 1.55, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + OriginalBidCPM: 1.55, + OriginalBidCur: "USD", + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + NetECPM: 1.55, + GrossECPM: 1.55, + OriginalCPM: 1.55, + OriginalCur: "USD", + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + }, + }, + }, + }, + { + name: "origbidcpmusd not present and revshare present", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 100, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.REVSHARE: "10", + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + OriginalBidCPM: 100, + OriginalBidCur: "USD", + }, + }, + }, + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PartnerID: 1, + PrebidBidderCode: "pubmatic", + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + NetECPM: 90, + GrossECPM: 100, + OriginalCPM: 100, + OriginalCur: "USD", + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + }, + }, + }, + }, + { + name: "origbidcpmusd is present", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 1.55, + }, + }, + }, + }, + Cur: "INR", + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + OriginalBidCPM: 125.76829, + OriginalBidCur: "INR", + OriginalBidCPMUSD: 1.76829, + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + NetECPM: 1.77, + GrossECPM: 1.77, + OriginalCPM: 125.77, + OriginalCur: "INR", + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + }, + }, + }, + }, + { + name: "origbidcpmusd not present for non-USD bids", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 125.16829, + }, + }, + }, + }, + Cur: "INR", + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + OriginalBidCPM: 125.16829, + OriginalBidCur: "INR", + }, + EG: 125.16829, + EN: 125.16829, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + GrossECPM: 125.17, + NetECPM: 125.17, + OriginalCPM: 125.17, + OriginalCur: "INR", + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + }, + }, + }, + }, + { + name: "origbidcpmusd is present, revshare is present", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 100, + }, + }, + }, + }, + Cur: "INR", + }, + }, + rCtx: &models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.REVSHARE: "10", + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + OriginalBidCPM: 200, + OriginalBidCur: "INR", + OriginalBidCPMUSD: 100, + }, + }, + }, + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PrebidBidderCode: "pubmatic", + PartnerID: 1, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + NetECPM: 90, + GrossECPM: 100, + OriginalCPM: 200, + OriginalCur: "INR", + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equal(t, tt.partners, partners, tt.name) + }) + } +} +func TestGetPartnerRecordsByImpForMarketPlaceBidders(t *testing.T) { + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + partners map[string][]PartnerRecord + }{ + { + name: "overwrite marketplace bid details", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "appnexus", + Bid: []openrtb2.Bid{ + {ID: "bid-id-1", ImpID: "imp1", Price: 1}, + }, + }, + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + {ID: "bid-id-2", ImpID: "imp1", Price: 2}, + }, + }, + { + Seat: "groupm", + Bid: []openrtb2.Bid{ + {ID: "bid-id-3", ImpID: "imp1", Price: 3}, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + MarketPlaceBidders: map[string]struct{}{ + "groupm": {}, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + Bidders: map[string]models.PartnerData{ + "appnexus": { + KGP: "apnx_kgp", + KGPV: "apnx_kgpv", + PrebidBidderCode: "appnexus", + }, + "pubmatic": { + KGP: "pubm_kgp", + KGPV: "pubm_kgpv", + PrebidBidderCode: "pubmatic", + }, + "groupm": { + KGP: "gm_kgp", + KGPV: "gm_kgpv", + PrebidBidderCode: "groupm", + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + GrossECPM: 1, + NetECPM: 1, + KGPV: "apnx_kgpv", + KGPSV: "apnx_kgpv", + }, + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-2", + OrigBidID: "bid-id-2", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + GrossECPM: 2, + NetECPM: 2, + KGPV: "pubm_kgpv", + KGPSV: "pubm_kgpv", + }, + { + PartnerID: "pubmatic", + BidderCode: "groupm", + PartnerSize: "0x0", + BidID: "bid-id-3", + OrigBidID: "bid-id-3", + DealID: "-1", + ServerSide: 1, + OriginalCur: models.USD, + GrossECPM: 3, + NetECPM: 3, + KGPV: "pubm_kgpv", + KGPSV: "pubm_kgpv", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + partners := getPartnerRecordsByImp(tt.args.ao, tt.args.rCtx) + assert.Equal(t, len(tt.partners), len(partners), tt.name) + for ind := range partners { + // ignore order of elements in slice while comparison + assert.ElementsMatch(t, partners[ind], tt.partners[ind], tt.name) + } + }) + } +} +func TestGetLogAuctionObjectAsURL(t *testing.T) { + + cfg := ow.cfg + defer func() { + ow.cfg = cfg + }() + + ow.cfg.Endpoint = "http://10.172.141.11/wl" + ow.cfg.PublicEndpoint = "http://t.pubmatic.com/wl" + + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + logInfo, forRespExt bool + } + type want struct { + logger string + header http.Header + } + tests := []struct { + name string + args args + want want + }{ + { + name: "do not prepare owlogger if pubid is missing", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + Endpoint: models.EndpointV25, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: "", + header: nil, + }, + }, + { + name: "do not prepare owlogger if bidrequest is nil", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: nil, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + Endpoint: models.EndpointV25, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: "", + header: nil, + }, + }, + { + name: "do not prepare owlogger if bidrequestwrapper is nil", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: nil, + }, + rCtx: &models.RequestCtx{ + Endpoint: models.EndpointV25, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: "", + header: nil, + }, + }, + { + name: "log integration type", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + Endpoint: models.EndpointV25, + PubID: 5890, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "log consent string", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + Ext: json.RawMessage(`{"consent": "any-random-consent-string"}`), + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","cns":"any-random-consent-string","sl":1,"dvc":{},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "log gdpr flag", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"gdpr":1}`), + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","gdpr":1,"sl":1,"dvc":{},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "log device platform", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + DevicePlatform: models.DevicePlatformMobileAppAndroid, + PubID: 5890, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{"plt":5},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "log device IFA Type", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + Ext: json.RawMessage(`{"ifa_type":"sspid"}`), + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + DevicePlatform: models.DevicePlatformMobileAppAndroid, + PubID: 5890, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{"plt":5,"ifty":8},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "log content from site object", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Content: &openrtb2.Content{ + ID: "1", + Title: "Game of thrones", + Cat: []string{"IAB-1"}, + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ct":{"id":"1","ttl":"Game of thrones","cat":["IAB-1"]},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "log content from app object", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{ + Content: &openrtb2.Content{ + ID: "1", + Title: "Game of thrones", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ct":{"id":"1","ttl":"Game of thrones"},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "log UA and IP in header", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + UA: "mozilla", + IP: "10.10.10.10", + KADUSERCookie: &http.Cookie{Name: "uids", Value: "eidsabcd"}, + PubID: 5890, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{"mozilla"}, + models.IP_HEADER: []string{"10.10.10.10"}, + }, + }, + }, + { + name: "loginfo is false", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "responseExt.Prebid is nil so floor details not set", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{ + Ext: json.RawMessage("{}"), + }, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.PublicEndpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "set floor details", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + Trackers: map[string]models.OWTracker{ + "any-bid-id": { + Tracker: models.Tracker{ + LoggerData: models.LoggerData{ + FloorProvider: "provider-1", + }, + }, + }, + }, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.PublicEndpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0,"fp":"provider-1"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger, header := GetLogAuctionObjectAsURL(tt.args.ao, tt.args.rCtx, tt.args.logInfo, tt.args.forRespExt) + logger, _ = url.QueryUnescape(logger) + assert.Equal(t, tt.want.logger, logger, tt.name) + assert.Equal(t, tt.want.header, header, tt.name) + }) + } +} +func TestGetLogAuctionObjectAsURLForFloorType(t *testing.T) { + cfg := ow.cfg + defer func() { + ow.cfg = cfg + }() + + ow.cfg.Endpoint = "http://10.172.141.11/wl" + ow.cfg.PublicEndpoint = "http://t.pubmatic.com/wl" + + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + logInfo, forRespExt bool + } + type want struct { + logger string + header http.Header + } + tests := []struct { + name string + args args + want want + }{ + { + name: "Floor type should be soft when prebid is nil", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + NewReqExt: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{}, + }, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "Floor type should be soft when prebid.floors is nil", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + NewReqExt: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{}, + }, + }, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "Floor type should be soft when prebid.floors is disabled", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + NewReqExt: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: ptrutil.ToPtr(false), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + }, + }, + }, + }, + }, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "Floor type should be soft when prebid.floors.enforcement is nil", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + NewReqExt: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: ptrutil.ToPtr(true), + }, + }, + }, + }, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "Floor type should be soft when prebid.floors.enforcement.enforcepbs is false", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + NewReqExt: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: ptrutil.ToPtr(true), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(false), + }, + }, + }, + }, + }, + PubID: 5890, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "Floor type should be hard when prebid.floors.enforcement.enforcepbs is true", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + NewReqExt: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: ptrutil.ToPtr(true), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + }, + }, + }, + }, + }, + PubID: 5890, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":1}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger, header := GetLogAuctionObjectAsURL(tt.args.ao, tt.args.rCtx, tt.args.logInfo, tt.args.forRespExt) + logger, _ = url.PathUnescape(logger) + assert.Equal(t, tt.want.logger, logger, tt.name) + assert.Equal(t, tt.want.header, header, tt.name) + }) + } +} +func TestGetLogAuctionObjectAsURLForFloorDetails(t *testing.T) { + cfg := ow.cfg + uuidFunc := getUUID + defer func() { + ow.cfg = cfg + getUUID = uuidFunc + }() + + getUUID = func() string { return "uuid" } + ow.cfg.Endpoint = "http://10.172.141.11/wl" + ow.cfg.PublicEndpoint = "http://t.pubmatic.com/wl" + + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + logInfo, forRespExt bool + } + type want struct { + logger string + header http.Header + } + tests := []struct { + name string + args args + want want + }{ + { + name: "set floor details from tracker when slots are absent", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + NewReqExt: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{}, + }, + Trackers: map[string]models.OWTracker{ + "any-bid": { + Tracker: models.Tracker{ + FloorSkippedFlag: ptrutil.ToPtr(1), + FloorModelVersion: "model-version", + FloorSource: ptrutil.ToPtr(2), + FloorType: 0, + LoggerData: models.LoggerData{ + FloorProvider: "provider", + FloorFetchStatus: ptrutil.ToPtr(3), + }, + }, + }, + }, + }, + logInfo: true, + forRespExt: true, + }, + want: want{ + logger: `http://t.pubmatic.com/wl?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"fmv":"model-version","fsrc":2,"ft":0,"ffs":3,"fp":"provider"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "set floor details from tracker when slots are present", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp-1", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + NewReqExt: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{}, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp-1": { + AdUnitName: "au", + SlotName: "sn", + }, + }, + Trackers: map[string]models.OWTracker{ + "any-bid": { + Tracker: models.Tracker{ + FloorSkippedFlag: ptrutil.ToPtr(1), + FloorModelVersion: "model-version", + FloorSource: ptrutil.ToPtr(2), + FloorType: 0, + LoggerData: models.LoggerData{ + FloorProvider: "provider", + FloorFetchStatus: ptrutil.ToPtr(3), + }, + }, + }, + }, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"uuid","sn":"sn","au":"au","ps":[],"fskp":1}],"dvc":{},"fmv":"model-version","fsrc":2,"ft":0,"ffs":3,"fp":"provider"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "set floor details from responseExt if tracker details are absent", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp-1", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + NewReqExt: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{}, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp-1": { + AdUnitName: "au", + SlotName: "sn", + }, + }, + ResponseExt: openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Skipped: ptrutil.ToPtr(true), + FetchStatus: openrtb_ext.FetchError, + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model-version", + }, + }, + FloorProvider: "provider", + }, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + }, + }, + }, + }, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"uuid","sn":"sn","au":"au","ps":[],"fskp":1}],"dvc":{},"fmv":"model-version","fsrc":2,"ft":1,"ffs":2,"fp":"provider"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger, header := GetLogAuctionObjectAsURL(tt.args.ao, tt.args.rCtx, tt.args.logInfo, tt.args.forRespExt) + logger, _ = url.PathUnescape(logger) + assert.Equal(t, tt.want.logger, logger, tt.name) + assert.Equal(t, tt.want.header, header, tt.name) + }) + } +} +func TestSlotRecordsInGetLogAuctionObjectAsURL(t *testing.T) { + cfg := ow.cfg + uuidFunc := getUUID + defer func() { + ow.cfg = cfg + getUUID = uuidFunc + }() + + getUUID = func() string { + return "sid" + } + + ow.cfg.Endpoint = "http://10.172.141.11/wl" + ow.cfg.PublicEndpoint = "http://t.pubmatic.com/wl" + + type args struct { + ao analytics.AuctionObject + rCtx *models.RequestCtx + logInfo, forRespExt bool + } + type want struct { + logger string + header http.Header + } + tests := []struct { + name string + args args + want want + }{ + { + name: "req.Imp not mapped in ImpBidCtx", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp1", + TagID: "tagid", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + Endpoint: models.EndpointV25, + PubID: 5890, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "multi imps request", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + TagID: "tagid_1", + }, + { + ID: "imp_2", + TagID: "tagid_2", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + SlotName: "imp_1_tagid_1", + AdUnitName: "tagid_1", + }, + "imp_2": { + AdUnitName: "tagid_2", + SlotName: "imp_2_tagid_2", + }, + }, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"sid","sn":"imp_1_tagid_1","au":"tagid_1","ps":[]},{"sid":"sid","sn":"imp_2_tagid_2","au":"tagid_2","ps":[]}],"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "multi imps request and one request has incomingslots", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + TagID: "tagid_1", + }, + { + ID: "imp_2", + TagID: "tagid_2", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + IncomingSlots: []string{"0x0v", "100x200"}, + IsRewardInventory: ptrutil.ToPtr(int8(1)), + SlotName: "imp_1_tagid_1", + AdUnitName: "tagid_1", + }, + "imp_2": { + AdUnitName: "tagid_2", + SlotName: "imp_2_tagid_2", + }, + }, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"sid","sn":"imp_1_tagid_1","sz":["0x0v","100x200"],"au":"tagid_1","ps":[],"rwrd":1},{"sid":"sid","sn":"imp_2_tagid_2","au":"tagid_2","ps":[]}],"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "multi imps request and one imp has partner record", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + TagID: "tagid_1", + }, + { + ID: "imp_2", + TagID: "tagid_2", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp_1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + IncomingSlots: []string{"0x0v", "100x200"}, + IsRewardInventory: ptrutil.ToPtr(int8(1)), + SlotName: "imp_1_tagid_1", + AdUnitName: "tagid_1", + }, + "imp_2": { + AdUnitName: "tagid_2", + SlotName: "imp_2_tagid_2", + }, + }, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"sid","sn":"imp_1_tagid_1","sz":["0x0v","100x200"],"au":"tagid_1",` + + `"ps":[{"pn":"pubmatic","bc":"pubmatic","kgpv":"","kgpsv":"","psz":"0x0","af":"","eg":0,"en":0,"l1":0,"l2":0,"t":0,"wb":0,"bidid":"bid-id-1",` + + `"origbidid":"bid-id-1","di":"-1","dc":"","db":0,"ss":1,"mi":0,"ocpm":0,"ocry":"USD"}],"rwrd":1},{"sid":"sid","sn":"imp_2_tagid_2","au":"tagid_2","ps":[]}],"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger, header := GetLogAuctionObjectAsURL(tt.args.ao, tt.args.rCtx, tt.args.logInfo, tt.args.forRespExt) + logger, _ = url.QueryUnescape(logger) + assert.Equal(t, tt.want.logger, logger, tt.name) + assert.Equal(t, tt.want.header, header, tt.name) + }) + } +} diff --git a/analytics/pubmatic/mhttp/http_util.go b/analytics/pubmatic/mhttp/http_util.go new file mode 100644 index 00000000000..747ee908081 --- /dev/null +++ b/analytics/pubmatic/mhttp/http_util.go @@ -0,0 +1,236 @@ +package mhttp + +// mhttp - Multi Http Calls +// mhttp like multi curl provides a wrapper interface over net/http client to +// create multiple http calls and fire them in parallel. Each http call is fired in +// a separate go routine and waits for all responses for a given timeout; +// All the response are captured automatically in respective individual HttpCall +// structures for further processing + +import ( + "bytes" + "io/ioutil" + + //"log" + "net/http" + "sync" + "sync/atomic" + "time" +) + +// Default Sizes +const ( + MAX_HTTP_CLIENTS = 10240 //Max HTTP live clients; clients are global reused in round-robin fashion + MAX_HTTP_CONNECTIONS = 1024 //For each HTTP client these many idle connections are created and resued + MAX_HTTP_CALLS = 200 //Only these many multi calls are considered at a time; + HTTP_CONNECT_TIMEOUT = 500 //Not used at present; + HTTP_RESPONSE_TIMEOUT = 2000 //Total response timeout = connect time + request write time + wait time + response read time +) + +var ( + // Global http client list; One client is used for every mutli call handle + clients [MAX_HTTP_CLIENTS]*http.Client + maxHttpClients int32 //No of http clients pre-created + maxHttpConnections int //no of idle connections per client + maxHttpCalls int //Max allowed http calls in parallel + nextClientIndex int32 //Index denotes next client index in round-robin +) + +//func dialTimeout(network, addr string) (net.Conn, error) { +// logger.Debug("Dialling...") +// timeout := time.Duration(time.Duration(HTTP_CONNECT_TIMEOUT) * time.Millisecond) +// return net.DialTimeout(network, addr, timeout) +//} + +// Init prepares the global list of http clients which are re-used in round-robin +// it should be called only once during start of the application before making any +// multi-http call +// +// maxClients : Max Http clients to create (<=MAX_HTTP_CLIENTS) +// maxConnections : Max idle Connections per client (<=MAX_HTTP_CONNECTIONS) +// maxHttpCalls : Max allowed http calls in parallel (<= MAX_HTTP_CALLS) +// respTimeout : http timeout +func Init(maxClients int32, maxConnections, maxCalls, respTimeout int) { + maxHttpClients = maxClients + maxHttpConnections = maxConnections + maxHttpCalls = maxCalls + + if maxHttpClients > MAX_HTTP_CLIENTS { + maxHttpClients = MAX_HTTP_CLIENTS + } + + if maxHttpConnections > MAX_HTTP_CONNECTIONS { + maxHttpConnections = MAX_HTTP_CONNECTIONS + } + + if maxHttpCalls > MAX_HTTP_CALLS { + maxHttpCalls = MAX_HTTP_CALLS + } + + if respTimeout <= 0 || respTimeout >= HTTP_RESPONSE_TIMEOUT { + respTimeout = HTTP_RESPONSE_TIMEOUT + } + + timeout := time.Duration(time.Duration(respTimeout) * time.Millisecond) + for i := int32(0); i < maxHttpClients; i++ { + //tr := &http.Transport{MaxIdleConnsPerHost: maxConnections, Dial: dialTimeout, ResponseHeaderTimeout: timeout} + tr := &http.Transport{DisableKeepAlives: false, MaxIdleConnsPerHost: maxHttpConnections} + clients[i] = &http.Client{Transport: tr, Timeout: timeout} + } + nextClientIndex = -1 +} + +type HttpCallInterface interface { + AddHeader(name, value string) + AddCookie(name, value string) + submit(wg *sync.WaitGroup) + getError() error + GetResponseBody() string +} + +// Wrapper to hold both http request and response data for a single http call +type HttpCall struct { + //Request Section + request *http.Request + + //Response Section + response *http.Response + err error + respBody string +} + +// create and returns a HttpCall object +func NewHttpCall(url string, postdata string) (hc *HttpCall, err error) { + hc = new(HttpCall) + hc.response = nil + hc.respBody = "" + hc.err = nil + method := "POST" + if postdata == "" { + method = "GET" + } + hc.request, hc.err = http.NewRequest(method, url, bytes.NewBuffer([]byte(postdata))) + //hc.request.Close = true + return +} + +// Appends an http header +func (hc *HttpCall) AddHeader(name, value string) { + hc.request.Header.Add(name, value) +} + +// Add http Cookie +func (hc *HttpCall) AddCookie(name, value string) { + cookie := http.Cookie{Name: name, Value: value} + hc.request.AddCookie(&cookie) +} + +// API call to get the reponse body in string format +func (hc *HttpCall) GetResponseBody() string { + return hc.respBody +} + +// API call to get error +func (hc *HttpCall) getError() error { + return hc.err +} + +// API call to get the reponse body in string format +func (hc *HttpCall) GetResponseHeader(hname string) string { + return hc.response.Header.Get(hname) +} + +// Get response headers map +func (hc *HttpCall) GetResponseHeaders() *http.Header { + return &hc.response.Header +} + +type MultiHttpContextInterface interface { + Execute() (vrc int, erc int) + AddHttpCall(hc HttpCallInterface) +} + +// MultiHttpContext is required to hold the information about all http calls to run +type MultiHttpContext struct { + hclist [MAX_HTTP_CALLS]HttpCallInterface + hccount int + wg sync.WaitGroup +} + +// Create a multi-http-context +func NewMultiHttpContext() *MultiHttpContext { + mhc := new(MultiHttpContext) + mhc.hccount = 0 + return mhc +} + +// Add a http call to multi-http-context +func (mhc *MultiHttpContext) AddHttpCall(hc HttpCallInterface) { + if mhc.hccount < maxHttpCalls { + mhc.hclist[mhc.hccount] = hc + mhc.hccount += 1 + } +} + +// Start firing parallel http calls that have been added so far +// Current go routine is blocked till it finishes with all http calls +// vrc: valid response count +// erc: error reponse count including timeouts +func (mhc *MultiHttpContext) Execute() (vrc int, erc int) { + vrc = 0 // Mark valid response count to zero + erc = 0 // Mark invalid response count to zero + if mhc.hccount <= 0 { + return + } + + mhc.wg.Add(mhc.hccount) //Set waitgroup count + for i := 0; i < mhc.hccount; i++ { + go mhc.hclist[i].submit(&mhc.wg) + } + mhc.wg.Wait() //Wait for all go routines to finish + + for i := 0; i < mhc.hccount; i++ { // validate each response + if mhc.hclist[i].getError() == nil && mhc.hclist[i].GetResponseBody() != "" { + vrc += 1 + } else { + erc += 1 + } + } + return vrc, erc +} + +// Get all the http calls from multi-http-context +func (mhc *MultiHttpContext) GetRequestsFromMultiHttpContext() [MAX_HTTP_CALLS]HttpCallInterface { + return mhc.hclist + +} + +///////////////////////////////////////////////////////////////////////////////// +/// Internal API calls +///////////////////////////////////////////////////////////////////////////////// + +// Internal api to get the next http client for use +func getNextHttpClient() *http.Client { + index := atomic.AddInt32(&nextClientIndex, 1) + if index >= maxHttpClients { + index = index % maxHttpClients + atomic.StoreInt32(&nextClientIndex, index) + } + return clients[index] +} + +// Internal API to fire individual http call +func (hc *HttpCall) submit(wg *sync.WaitGroup) { + defer wg.Done() + client := getNextHttpClient() + hc.response, hc.err = client.Do(hc.request) + //logger.Debug("ADCALL RESPONSE :%v" , hc.response) + if hc.err != nil { + hc.respBody = "" + return + } + defer hc.response.Body.Close() + body, err := ioutil.ReadAll(hc.response.Body) + hc.respBody = string(body) + hc.err = err +} diff --git a/analytics/pubmatic/mhttp/http_util_test.go b/analytics/pubmatic/mhttp/http_util_test.go new file mode 100644 index 00000000000..381e937591e --- /dev/null +++ b/analytics/pubmatic/mhttp/http_util_test.go @@ -0,0 +1,162 @@ +package mhttp + +import ( + "testing" + + // mock_pubmatic "github.com/prebid/prebid-server/analytics/pubmatic/mock" + + "github.com/stretchr/testify/assert" +) + +func TestNewHttpCall(t *testing.T) { + type args struct { + url string + postdata string + } + type want struct { + method string + err bool + } + tests := []struct { + name string + args args + want want + }{ + { + name: "test GET method", + args: args{ + url: "http://t.pubmatic.com", + postdata: "", + }, + want: want{ + method: "GET", + }, + }, + { + name: "test POST method", + args: args{ + url: "http://t.pubmatic.com/wl", + postdata: "any-data", + }, + want: want{ + method: "POST", + }, + }, + { + name: "test invalid url", + args: args{ + url: "http://invalid-url param=12;", + postdata: "any-data", + }, + want: want{ + method: "POST", + err: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hc, err := NewHttpCall(tt.args.url, tt.args.postdata) + assert.NotNil(t, hc, tt.name) + assert.Nil(t, err, tt.name) + if !tt.want.err { + assert.Equal(t, tt.want.method, hc.request.Method, tt.name) + } + assert.Equal(t, tt.want.err, hc.err != nil, tt.name) + }) + } +} + +func TestAddHeadersAndCookies(t *testing.T) { + type args struct { + headerKey, headerValue string + cookieKey, cookieValue string + } + + tests := []struct { + name string + args args + }{ + { + name: "test add headers and cookies", + args: args{ + headerKey: "header-key", + headerValue: "header-val", + cookieKey: "cookie-key", + cookieValue: "cookie-val", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hc, _ := NewHttpCall("t.pubmatic.com", "") + hc.AddHeader(tt.args.headerKey, tt.args.headerValue) + hc.AddCookie(tt.args.cookieKey, tt.args.cookieValue) + assert.Equal(t, tt.args.headerValue, hc.request.Header.Get(tt.args.headerKey), tt.name) + cookie, _ := hc.request.Cookie(tt.args.cookieKey) + assert.Equal(t, tt.args.cookieValue, cookie.Value, tt.name) + }) + } +} + +func TestNewMultiHttpContextAndAddHttpCall(t *testing.T) { + mhc := NewMultiHttpContext() + assert.NotNil(t, mhc) + assert.Equal(t, mhc.hccount, 0) + maxHttpCalls = 1 + mhc.AddHttpCall(&HttpCall{}) + mhc.AddHttpCall(&HttpCall{}) + mhc.AddHttpCall(&HttpCall{}) + assert.Equal(t, mhc.hccount, 1) +} + +func TestInit(t *testing.T) { + type args struct { + maxClients int32 + maxConnections, maxCalls, respTimeout int + } + type want struct { + maxHttpClients int32 + maxHttpConnections, maxHttpCalls int + } + tests := []struct { + name string + args args + want want + }{ + { + name: "valid values in accepted range", + args: args{ + maxClients: 1, + maxConnections: 2, + maxCalls: 3, + respTimeout: 10, + }, + want: want{ + maxHttpClients: 1, + maxHttpConnections: 2, + maxHttpCalls: 3, + }, + }, + { + name: "values not in the accepted range", + args: args{ + maxClients: 11111, + maxConnections: 2000, + maxCalls: 300, + respTimeout: 10000, + }, + want: want{ + maxHttpClients: 10240, + maxHttpConnections: 1024, + maxHttpCalls: 200, + }, + }, + } + for _, tt := range tests { + Init(tt.args.maxClients, tt.args.maxConnections, tt.args.maxCalls, tt.args.respTimeout) + assert.Equal(t, tt.want.maxHttpClients, maxHttpClients, tt.name) + assert.Equal(t, tt.want.maxHttpConnections, maxHttpConnections, tt.name) + assert.Equal(t, tt.want.maxHttpCalls, maxHttpCalls, tt.name) + } +} diff --git a/analytics/pubmatic/mhttp/mock/mock.go b/analytics/pubmatic/mhttp/mock/mock.go new file mode 100644 index 00000000000..1c898851934 --- /dev/null +++ b/analytics/pubmatic/mhttp/mock/mock.go @@ -0,0 +1,149 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/PubMatic-OpenWrap/prebid-server/analytics/pubmatic/mhttp (interfaces: HttpCallInterface,MultiHttpContextInterface) + +// Package mock_mhttp is a generated GoMock package. +package mock_mhttp + +import ( + gomock "github.com/golang/mock/gomock" + mhttp "github.com/prebid/prebid-server/v2/analytics/pubmatic/mhttp" + reflect "reflect" + sync "sync" +) + +// MockHttpCallInterface is a mock of HttpCallInterface interface +type MockHttpCallInterface struct { + ctrl *gomock.Controller + recorder *MockHttpCallInterfaceMockRecorder +} + +// MockHttpCallInterfaceMockRecorder is the mock recorder for MockHttpCallInterface +type MockHttpCallInterfaceMockRecorder struct { + mock *MockHttpCallInterface +} + +// NewMockHttpCallInterface creates a new mock instance +func NewMockHttpCallInterface(ctrl *gomock.Controller) *MockHttpCallInterface { + mock := &MockHttpCallInterface{ctrl: ctrl} + mock.recorder = &MockHttpCallInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockHttpCallInterface) EXPECT() *MockHttpCallInterfaceMockRecorder { + return m.recorder +} + +// AddCookie mocks base method +func (m *MockHttpCallInterface) AddCookie(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddCookie", arg0, arg1) +} + +// AddCookie indicates an expected call of AddCookie +func (mr *MockHttpCallInterfaceMockRecorder) AddCookie(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddCookie", reflect.TypeOf((*MockHttpCallInterface)(nil).AddCookie), arg0, arg1) +} + +// AddHeader mocks base method +func (m *MockHttpCallInterface) AddHeader(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddHeader", arg0, arg1) +} + +// AddHeader indicates an expected call of AddHeader +func (mr *MockHttpCallInterfaceMockRecorder) AddHeader(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddHeader", reflect.TypeOf((*MockHttpCallInterface)(nil).AddHeader), arg0, arg1) +} + +// GetResponseBody mocks base method +func (m *MockHttpCallInterface) GetResponseBody() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetResponseBody") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetResponseBody indicates an expected call of GetResponseBody +func (mr *MockHttpCallInterfaceMockRecorder) GetResponseBody() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResponseBody", reflect.TypeOf((*MockHttpCallInterface)(nil).GetResponseBody)) +} + +// getError mocks base method +func (m *MockHttpCallInterface) getError() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getError") + ret0, _ := ret[0].(error) + return ret0 +} + +// getError indicates an expected call of getError +func (mr *MockHttpCallInterfaceMockRecorder) getError() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getError", reflect.TypeOf((*MockHttpCallInterface)(nil).getError)) +} + +// submit mocks base method +func (m *MockHttpCallInterface) submit(arg0 *sync.WaitGroup) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "submit", arg0) +} + +// submit indicates an expected call of submit +func (mr *MockHttpCallInterfaceMockRecorder) submit(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "submit", reflect.TypeOf((*MockHttpCallInterface)(nil).submit), arg0) +} + +// MockMultiHttpContextInterface is a mock of MultiHttpContextInterface interface +type MockMultiHttpContextInterface struct { + ctrl *gomock.Controller + recorder *MockMultiHttpContextInterfaceMockRecorder +} + +// MockMultiHttpContextInterfaceMockRecorder is the mock recorder for MockMultiHttpContextInterface +type MockMultiHttpContextInterfaceMockRecorder struct { + mock *MockMultiHttpContextInterface +} + +// NewMockMultiHttpContextInterface creates a new mock instance +func NewMockMultiHttpContextInterface(ctrl *gomock.Controller) *MockMultiHttpContextInterface { + mock := &MockMultiHttpContextInterface{ctrl: ctrl} + mock.recorder = &MockMultiHttpContextInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockMultiHttpContextInterface) EXPECT() *MockMultiHttpContextInterfaceMockRecorder { + return m.recorder +} + +// AddHttpCall mocks base method +func (m *MockMultiHttpContextInterface) AddHttpCall(arg0 mhttp.HttpCallInterface) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddHttpCall", arg0) +} + +// AddHttpCall indicates an expected call of AddHttpCall +func (mr *MockMultiHttpContextInterfaceMockRecorder) AddHttpCall(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddHttpCall", reflect.TypeOf((*MockMultiHttpContextInterface)(nil).AddHttpCall), arg0) +} + +// Execute mocks base method +func (m *MockMultiHttpContextInterface) Execute() (int, int) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Execute") + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(int) + return ret0, ret1 +} + +// Execute indicates an expected call of Execute +func (mr *MockMultiHttpContextInterfaceMockRecorder) Execute() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockMultiHttpContextInterface)(nil).Execute)) +} diff --git a/analytics/pubmatic/models.go b/analytics/pubmatic/models.go new file mode 100644 index 00000000000..aa43d9da04e --- /dev/null +++ b/analytics/pubmatic/models.go @@ -0,0 +1,133 @@ +package pubmatic + +import ( + "encoding/json" + "fmt" + "math" + "net/url" + "regexp" + "strconv" + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +const ( + //constant for adformat + Banner = "banner" + Video = "video" + Native = "native" + + REVSHARE = "rev_share" + BID_PRECISION = 2 +) + +func GetSize(width, height int64) string { + return fmt.Sprintf("%dx%d", width, height) +} + +// CreatePartnerKey returns key with partner appended +func CreatePartnerKey(partner, key string) string { + if partner == "" { + return key + } + return key + "_" + partner +} + +// GetAdFormat gets adformat from creative(adm) of the bid +func GetAdFormat(adm string) string { + adFormat := Banner + videoRegex, _ := regexp.Compile(" 0 { + partnerRecord.MetaData = &MetaData{ + NetworkID: meta.NetworkID, + AdvertiserID: meta.AdvertiserID, + PrimaryCategoryID: meta.PrimaryCategoryID, + AgencyID: meta.AgencyID, + DemandSource: meta.DemandSource, + SecondaryCategoryIDs: meta.SecondaryCategoryIDs, + } + } + //NOTE : We Don't get following Data points in Response, whenever got from translator, + //they can be populated. + //partnerRecord.MetaData.NetworkName = meta.NetworkName + //partnerRecord.MetaData.AdvertiserName = meta.AdvertiserName + //partnerRecord.MetaData.AgencyName = meta.AgencyName + //partnerRecord.MetaData.BrandName = meta.BrandName + //partnerRecord.MetaData.BrandID = meta.BrandID + //partnerRecord.MetaData.DChain = meta.DChain (type is json.RawMessage) +} diff --git a/analytics/pubmatic/record_test.go b/analytics/pubmatic/record_test.go new file mode 100644 index 00000000000..7174074781a --- /dev/null +++ b/analytics/pubmatic/record_test.go @@ -0,0 +1,519 @@ +package pubmatic + +import ( + "encoding/json" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestLogDeviceObject(t *testing.T) { + type args struct { + ortbBidRequest *openrtb2.BidRequest + rctx *models.RequestCtx + } + + type want struct { + device Device + } + + tests := []struct { + name string + args args + want want + }{ + { + name: `Nil request`, + args: args{ + rctx: &models.RequestCtx{ + DevicePlatform: models.DevicePlatformDesktop, + }, + }, + want: want{ + device: Device{ + Platform: models.DevicePlatformDesktop, + }, + }, + }, + { + name: `Empty uaFromHTTPReq`, + args: args{ + ortbBidRequest: &openrtb2.BidRequest{}, + rctx: &models.RequestCtx{ + DevicePlatform: models.DevicePlatformMobileWeb, + }, + }, + want: want{ + device: Device{ + Platform: models.DevicePlatformMobileWeb, + }, + }, + }, + { + name: `Invalid device ext`, + args: args{ + ortbBidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + Ext: json.RawMessage(`invalid ext`), + }, + }, + rctx: &models.RequestCtx{ + DevicePlatform: models.DevicePlatformMobileWeb, + }, + }, + want: want{ + device: Device{ + Platform: 0, + }, + }, + }, + { + name: `IFA Type key absent`, + args: args{ + ortbBidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + Ext: json.RawMessage(`{"anykey":"anyval"}`), + }, + }, + rctx: &models.RequestCtx{ + DevicePlatform: models.DevicePlatformMobileWeb, + }, + }, + want: want{ + device: Device{ + Platform: models.DevicePlatformMobileWeb, + }, + }, + }, + { + name: `Invalid data type for ifa_type key`, + args: args{ + ortbBidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + Ext: json.RawMessage(`{"ifa_type": 123}`)}, + }, + rctx: &models.RequestCtx{ + DevicePlatform: models.DevicePlatformMobileWeb, + }, + }, + want: want{ + device: Device{ + Platform: models.DevicePlatformMobileWeb, + }, + }, + }, + { + name: `ifa_type missing in DeviceIFATypeID mapping`, + args: args{ + ortbBidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + Ext: json.RawMessage(`{"ifa_type": "anything"}`), + }, + }, + rctx: &models.RequestCtx{ + DevicePlatform: models.DevicePlatformMobileWeb, + }, + }, + want: want{ + device: Device{ + Platform: models.DevicePlatformMobileWeb, + IFAType: ptrutil.ToPtr(0), + }, + }, + }, + { + name: `Case insensitive ifa_type`, + args: args{ + ortbBidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + Ext: json.RawMessage(`{"ifa_type": "DpId"}`), + }, + }, + rctx: &models.RequestCtx{ + DevicePlatform: models.DevicePlatformMobileWeb, + }, + }, + want: want{ + device: Device{ + Platform: models.DevicePlatformMobileWeb, + IFAType: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeDPID]), + }, + }, + }, + { + name: `Valid ifa_type`, + args: args{ + ortbBidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + Ext: json.RawMessage(`{"ifa_type": "sessionid"}`), + }, + }, + rctx: &models.RequestCtx{ + DevicePlatform: models.DevicePlatformMobileWeb, + }, + }, + want: want{ + device: Device{ + Platform: models.DevicePlatformMobileWeb, + IFAType: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeSESSIONID]), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + wlog := &WloggerRecord{} + wlog.logDeviceObject(tt.args.rctx, tt.args.ortbBidRequest) + assert.Equal(t, tt.want.device, wlog.Device) + }) + } +} + +func TestLogIntegrationType(t *testing.T) { + tests := []struct { + name string + endpoint string + integrationType string + }{ + { + name: "sdk", + endpoint: models.EndpointV25, + integrationType: models.TypeSDK, + }, + { + name: "amp", + endpoint: models.EndpointAMP, + integrationType: models.TypeAmp, + }, + { + name: "ctv-vast", + endpoint: models.EndpointVAST, + integrationType: models.TypeTag, + }, + { + name: "ctv-ortb", + endpoint: models.EndpointORTB, + integrationType: models.TypeS2S, + }, + { + name: "ctv-json", + endpoint: models.EndpointJson, + integrationType: models.TypeInline, + }, + { + name: "openrtb-video", + endpoint: models.EndpointVideo, + integrationType: models.TypeInline, + }, + { + name: "invalid", + endpoint: "invalid", + integrationType: "", + }, + { + name: "ows2s", + endpoint: models.EndpointWebS2S, + integrationType: models.TypeWebS2S, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + wlog := WloggerRecord{} + wlog.logIntegrationType(tt.endpoint) + assert.Equal(t, tt.integrationType, wlog.IntegrationType, tt.name) + }) + } +} + +func TestLogFloorType(t *testing.T) { + tests := []struct { + name string + prebidExt *openrtb_ext.ExtRequestPrebid + floorType int + }{ + { + name: "Nil prebidExt", + prebidExt: nil, + floorType: models.SoftFloor, + }, + { + name: "Nil prebidExt.Floors", + prebidExt: &openrtb_ext.ExtRequestPrebid{}, + floorType: models.SoftFloor, + }, + { + name: "Nil prebidExt.Floors.Enabled", + prebidExt: &openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{}, + }, + floorType: models.SoftFloor, + }, + { + name: "false prebidExt.Floors.Enabled", + prebidExt: &openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: ptrutil.ToPtr(false), + }, + }, + floorType: models.SoftFloor, + }, + { + name: "Nil prebidExt.Floors.Enabled.Enforcement", + prebidExt: &openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: ptrutil.ToPtr(true), + Enforcement: nil, + }, + }, + floorType: models.SoftFloor, + }, + { + name: "Nil prebidExt.Floors.Enabled.Enforcement.EnforcePBS", + prebidExt: &openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: ptrutil.ToPtr(true), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: nil, + }, + }, + }, + floorType: models.SoftFloor, + }, + { + name: "false prebidExt.Floors.Enabled.Enforcement.EnforcePBS", + prebidExt: &openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: ptrutil.ToPtr(true), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(false), + }, + }, + }, + floorType: models.SoftFloor, + }, + { + name: "true prebidExt.Floors.Enabled.Enforcement.EnforcePBS", + prebidExt: &openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: ptrutil.ToPtr(true), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + }, + }, + }, + floorType: models.HardFloor, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + wlog := WloggerRecord{} + wlog.logFloorType(tt.prebidExt) + assert.Equal(t, tt.floorType, wlog.FloorType, tt.name) + }) + } +} + +func TestLogContentObject(t *testing.T) { + type args struct { + content *openrtb2.Content + } + tests := []struct { + name string + args args + want *Content + }{ + { + name: "Empty", + args: args{}, + want: nil, + }, + { + name: "OnlyID", + args: args{ + content: &openrtb2.Content{ + ID: "ID", + }, + }, + want: &Content{ + ID: "ID", + }, + }, + { + name: "OnlyEpisode", + args: args{ + content: &openrtb2.Content{ + Episode: 123, + }, + }, + want: &Content{ + Episode: 123, + }, + }, + { + name: "OnlyTitle", + args: args{ + content: &openrtb2.Content{ + Title: "Title", + }, + }, + want: &Content{ + Title: "Title", + }, + }, + { + name: "OnlySeries", + args: args{ + content: &openrtb2.Content{ + Series: "Series", + }, + }, + want: &Content{ + Series: "Series", + }, + }, + { + name: "OnlySeason", + args: args{ + content: &openrtb2.Content{ + Season: "Season", + }, + }, + want: &Content{ + Season: "Season", + }, + }, + { + name: "OnlyCat", + args: args{ + content: &openrtb2.Content{ + Cat: []string{"CAT-1"}, + }, + }, + want: &Content{ + Cat: []string{"CAT-1"}, + }, + }, + { + name: "AllPresent", + args: args{ + content: &openrtb2.Content{ + ID: "ID", + Episode: 123, + Title: "Title", + Series: "Series", + Season: "Season", + Cat: []string{"CAT-1"}, + }, + }, + want: &Content{ + ID: "ID", + Episode: 123, + Title: "Title", + Series: "Series", + Season: "Season", + Cat: []string{"CAT-1"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + wlog := &WloggerRecord{} + wlog.logContentObject(tt.args.content) + assert.Equal(t, tt.want, wlog.Content) + }) + } +} + +func TestSetMetaDataObject(t *testing.T) { + type args struct { + meta *openrtb_ext.ExtBidPrebidMeta + partnerRecord *PartnerRecord + } + tests := []struct { + name string + args args + partnerRecord *PartnerRecord + }{ + { + name: "NetworkID 0, AdvertiserID 0, SecondaryCategoryIDs size 0", + args: args{ + meta: &openrtb_ext.ExtBidPrebidMeta{ + NetworkID: 0, + AdvertiserID: 0, + SecondaryCategoryIDs: []string{}, + }, + partnerRecord: &PartnerRecord{ + PartnerID: "pubmatic", + }, + }, + partnerRecord: &PartnerRecord{ + PartnerID: "pubmatic", + }, + }, + { + name: "NetworkID other than 0", + args: args{ + meta: &openrtb_ext.ExtBidPrebidMeta{ + NetworkID: 10, + AdvertiserID: 0, + }, + partnerRecord: &PartnerRecord{ + PartnerID: "pubmatic", + }, + }, + partnerRecord: &PartnerRecord{ + PartnerID: "pubmatic", + MetaData: &MetaData{ + NetworkID: 10, + }, + }, + }, + { + name: "AdvertiserID other than 0", + args: args{ + meta: &openrtb_ext.ExtBidPrebidMeta{ + NetworkID: 0, + AdvertiserID: 10, + }, + partnerRecord: &PartnerRecord{ + PartnerID: "pubmatic", + }, + }, + partnerRecord: &PartnerRecord{ + PartnerID: "pubmatic", + MetaData: &MetaData{ + AdvertiserID: 10, + }, + }, + }, + { + name: "SecondaryCategoryIDs size other than 0", + args: args{ + meta: &openrtb_ext.ExtBidPrebidMeta{ + NetworkID: 0, + AdvertiserID: 0, + SecondaryCategoryIDs: []string{"cat1"}, + }, + partnerRecord: &PartnerRecord{ + PartnerID: "pubmatic", + }, + }, + partnerRecord: &PartnerRecord{ + PartnerID: "pubmatic", + MetaData: &MetaData{ + SecondaryCategoryIDs: []string{"cat1"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.args.partnerRecord.setMetaDataObject(tt.args.meta) + assert.Equal(t, tt.partnerRecord, tt.args.partnerRecord, tt.name) + }) + } +} diff --git a/config/account.go b/config/account.go index ee131873e35..beac5a99edf 100644 --- a/config/account.go +++ b/config/account.go @@ -33,7 +33,7 @@ type Account struct { DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"` DefaultIntegration string `mapstructure:"default_integration" json:"default_integration"` CookieSync CookieSync `mapstructure:"cookie_sync" json:"cookie_sync"` - Events Events `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725 + Events Events `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/v2/issues/1725 TruncateTargetAttribute *int `mapstructure:"truncate_target_attr" json:"truncate_target_attr"` AlternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes `mapstructure:"alternatebiddercodes" json:"alternatebiddercodes"` Hooks AccountHooks `mapstructure:"hooks" json:"hooks"` diff --git a/config/config.go b/config/config.go index 60a541e2ed0..cd142023201 100644 --- a/config/config.go +++ b/config/config.go @@ -100,6 +100,10 @@ type Configuration struct { Hooks Hooks `mapstructure:"hooks"` Validations Validations `mapstructure:"validations"` PriceFloors PriceFloors `mapstructure:"price_floors"` + + TrackerURL string `mapstructure:"tracker_url"` + VendorListScheduler VendorListScheduler `mapstructure:"vendor_list_scheduler"` + PriceFloorFetcher PriceFloorFetcher `mapstructure:"price_floor_fetcher"` } type PriceFloors struct { @@ -115,6 +119,12 @@ type PriceFloorFetcher struct { MaxRetries int `mapstructure:"max_retries"` } +type VendorListScheduler struct { + Enabled bool `mapstructure:"enabled"` + Interval string `mapstructure:"interval"` + Timeout string `mapstructure:"timeout"` +} + const MIN_COOKIE_SIZE_BYTES = 500 type HTTPClient struct { @@ -122,6 +132,11 @@ type HTTPClient struct { MaxIdleConns int `mapstructure:"max_idle_connections"` MaxIdleConnsPerHost int `mapstructure:"max_idle_connections_per_host"` IdleConnTimeout int `mapstructure:"idle_connection_timeout_seconds"` + + TLSHandshakeTimeout int `mapstructure:"tls_handshake_timeout"` + ResponseHeaderTimeout int `mapstructure:"response_header_timeout"` + DialTimeout int `mapstructure:"dial_timeout"` + DialKeepAlive int `mapstructure:"dial_keepalive"` } func (cfg *Configuration) validate(v *viper.Viper) []error { @@ -442,8 +457,9 @@ type LMT struct { } type Analytics struct { - File FileLogs `mapstructure:"file"` - Pubstack Pubstack `mapstructure:"pubstack"` + File FileLogs `mapstructure:"file"` + Pubstack Pubstack `mapstructure:"pubstack"` + PubMatic PubMaticWL `mapstructure:"pubmatic"` } type CurrencyConverter struct { @@ -472,6 +488,17 @@ type Pubstack struct { ConfRefresh string `mapstructure:"configuration_refresh_delay"` } +type PubMaticWL struct { + Enabled bool `mapstructure:"enabled"` + Endpoint string + PublicEndpoint string + MaxClients int32 + MaxConnections int + MaxCalls int + RespTimeout int + Client HTTPClient `mapstructure:"http_client"` +} + type PubstackBuffer struct { BufferSize string `mapstructure:"size"` EventCount int `mapstructure:"count"` @@ -845,9 +872,7 @@ func (cfg *Configuration) GetCachedAssetURL(uuid string) string { // Set the default config values for the viper object we are using. func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { if filename != "" { - v.SetConfigName(filename) - v.AddConfigPath(".") - v.AddConfigPath("/etc/config") + v.SetConfigFile(filename) } // Fixes #475: Some defaults will be set just so they are accessible via environment variables @@ -1090,9 +1115,10 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("blacklisted_apps", []string{""}) v.SetDefault("blacklisted_accts", []string{""}) v.SetDefault("account_required", false) + v.SetDefault("account_defaults.disabled", false) v.SetDefault("account_defaults.debug_allow", true) - v.SetDefault("account_defaults.price_floors.enabled", false) + v.SetDefault("account_defaults.price_floors.enabled", true) v.SetDefault("account_defaults.price_floors.enforce_floors_rate", 100) v.SetDefault("account_defaults.price_floors.adjust_for_bid_adjustment", true) v.SetDefault("account_defaults.price_floors.enforce_deal_floors", false) @@ -1212,6 +1238,9 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { for bidderName := range bidderInfos { setBidderDefaults(v, strings.ToLower(bidderName)) } + //Defaults for Price floor fetcher + v.SetDefault("price_floor_fetcher.worker", 20) + v.SetDefault("price_floor_fetcher.capacity", 20000) } func isConfigInfoPresent(v *viper.Viper, prefix string, fields []string) bool { diff --git a/config/config_test.go b/config/config_test.go index a551c1be66e..87240282b0e 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -188,7 +188,7 @@ func TestDefaults(t *testing.T) { cmpBools(t, "compression.request.enable_gzip", false, cfg.Compression.Request.GZIP) cmpBools(t, "compression.response.enable_gzip", false, cfg.Compression.Response.GZIP) - cmpBools(t, "account_defaults.price_floors.enabled", false, cfg.AccountDefaults.PriceFloors.Enabled) + cmpBools(t, "account_defaults.price_floors.enabled", true, cfg.AccountDefaults.PriceFloors.Enabled) cmpInts(t, "account_defaults.price_floors.enforce_floors_rate", 100, cfg.AccountDefaults.PriceFloors.EnforceFloorsRate) cmpBools(t, "account_defaults.price_floors.adjust_for_bid_adjustment", true, cfg.AccountDefaults.PriceFloors.AdjustForBidAdjustment) cmpBools(t, "account_defaults.price_floors.enforce_deal_floors", false, cfg.AccountDefaults.PriceFloors.EnforceDealFloors) @@ -205,6 +205,14 @@ func TestDefaults(t *testing.T) { cmpInts(t, "account_defaults.price_floors.fetch.max_schema_dims", 0, cfg.AccountDefaults.PriceFloors.Fetcher.MaxSchemaDims) cmpBools(t, "account_defaults.events.enabled", false, cfg.AccountDefaults.Events.Enabled) + cmpBools(t, "account_defaults.price_floors.fetch.enabled", false, cfg.AccountDefaults.PriceFloors.Fetcher.Enabled) + cmpInts(t, "account_defaults.price_floors.fetch.timeout_ms", 3000, cfg.AccountDefaults.PriceFloors.Fetcher.Timeout) + cmpInts(t, "account_defaults.price_floors.fetch.max_file_size_kb", 100, cfg.AccountDefaults.PriceFloors.Fetcher.MaxFileSizeKB) + cmpInts(t, "account_defaults.price_floors.fetch.max_rules", 1000, cfg.AccountDefaults.PriceFloors.Fetcher.MaxRules) + cmpInts(t, "account_defaults.price_floors.fetch.max_age_sec", 86400, cfg.AccountDefaults.PriceFloors.Fetcher.MaxAge) + cmpInts(t, "account_defaults.price_floors.fetch.period_sec", 3600, cfg.AccountDefaults.PriceFloors.Fetcher.Period) + cmpInts(t, "price_floor_fetcher.worker", 20, cfg.PriceFloorFetcher.Worker) + cmpInts(t, "price_floor_fetcher.capacity", 20000, cfg.PriceFloorFetcher.Capacity) cmpBools(t, "hooks.enabled", false, cfg.Hooks.Enabled) cmpStrings(t, "validations.banner_creative_max_size", "skip", cfg.Validations.BannerCreativeMaxSize) @@ -503,6 +511,9 @@ account_defaults: anon_keep_bits: 50 ipv4: anon_keep_bits: 20 +price_floor_fetcher: + worker: 10 + capacity: 20 tmax_adjustments: enabled: true bidder_response_duration_min_ms: 700 @@ -618,6 +629,9 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "account_defaults.price_floors.fetch.max_schema_dims", 10, cfg.AccountDefaults.PriceFloors.Fetcher.MaxSchemaDims) cmpBools(t, "account_defaults.events.enabled", true, cfg.AccountDefaults.Events.Enabled) + cmpBools(t, "account_defaults.price_floors.fetch.enabled", true, cfg.AccountDefaults.PriceFloors.Fetcher.Enabled) + cmpInts(t, "price_floor_fetcher.worker", 10, cfg.PriceFloorFetcher.Worker) + cmpInts(t, "price_floor_fetcher.capacity", 20, cfg.PriceFloorFetcher.Capacity) cmpInts(t, "account_defaults.privacy.ipv6.anon_keep_bits", 50, cfg.AccountDefaults.Privacy.IPv6Config.AnonKeepBits) cmpInts(t, "account_defaults.privacy.ipv4.anon_keep_bits", 20, cfg.AccountDefaults.Privacy.IPv4Config.AnonKeepBits) diff --git a/docs/developers/automated-tests.md b/docs/developers/automated-tests.md index 6814fba385c..9e435aaf57e 100644 --- a/docs/developers/automated-tests.md +++ b/docs/developers/automated-tests.md @@ -15,7 +15,7 @@ For more info on how to write tests in Go, see [the Go docs](https://golang.org/ ## Adapter Tests If your adapter makes HTTP calls using standard JSON, you should use the -[RunJSONBidderTest](https://github.com/prebid/prebid-server/blob/master/adapters/adapterstest/test_json.go#L50) function. +[RunJSONBidderTest](https://github.com/PubMatic-OpenWrap/prebid-server/blob/master/adapters/adapterstest/test_json.go#L50) function. This will be much more thorough, convenient, maintainable, and reusable than writing standard Go tests for your adapter. diff --git a/docs/developers/code-reviews.md b/docs/developers/code-reviews.md index d8ee820cd80..59455d3b8ad 100644 --- a/docs/developers/code-reviews.md +++ b/docs/developers/code-reviews.md @@ -1,9 +1,9 @@ # Code Reviews ## Standards -Anyone is free to review and comment on any [open pull requests](https://github.com/prebid/prebid-server/pulls). +Anyone is free to review and comment on any [open pull requests](https://github.com/PubMatic-OpenWrap/prebid-server/pulls). -All pull requests must be reviewed and approved by at least one [core member](https://github.com/orgs/prebid/teams/core/members) before merge. +All pull requests must be reviewed and approved by at least one [core member](https://github.com/orgs/PubMatic-OpenWrap/teams/core/members) before merge. Very small pull requests may be merged with just one review if they: @@ -38,7 +38,7 @@ Some examples include: - Can we improve the user's experience in any way? - Have the relevant [docs](..) been added or updated? If not, add the `needs docs` label. - Do you believe that the code works by looking at the unit tests? If not, suggest more tests until you do! -- Is the motivation behind these changes clear? If not, there must be [an issue](https://github.com/prebid/prebid-server/issues) explaining it. Are there better ways to achieve those goals? +- Is the motivation behind these changes clear? If not, there must be [an issue](https://github.com/PubMatic-OpenWrap/prebid-server/issues) explaining it. Are there better ways to achieve those goals? - Does the code use any global, mutable state? [Inject dependencies](https://en.wikipedia.org/wiki/Dependency_injection) instead! - Can the code be organized into smaller, more modular pieces? - Is there dead code which can be deleted? Or TODO comments which should be resolved? diff --git a/docs/developers/contributing.md b/docs/developers/contributing.md index 2a6a574ed14..cc2daaecd11 100644 --- a/docs/developers/contributing.md +++ b/docs/developers/contributing.md @@ -2,7 +2,7 @@ ## Create an issue -[Create an issue](https://github.com/prebid/prebid-server/issues/new) describing the motivation for your changes. +[Create an issue](https://github.com/PubMatic-OpenWrap/prebid-server/issues/new) describing the motivation for your changes. Are you fixing a bug? Improving documentation? Optimizing some slow code? Pull Requests without associated Issues may still be accepted, if the motivation is obvious. @@ -38,7 +38,7 @@ those updates must be submitted in the same Pull Request as the code changes. ## Open a Pull Request When you're ready, [submit a Pull Request](https://help.github.com/articles/creating-a-pull-request/) -against the `master` branch of [our GitHub repository](https://github.com/prebid/prebid-server/compare). +against the `master` branch of [our GitHub repository](https://github.com/PubMatic-OpenWrap/prebid-server/compare). Pull Requests will be vetted through GitHub Actions. To reproduce these same tests locally, do: @@ -49,5 +49,5 @@ To reproduce these same tests locally, do: If the tests pass locally, but fail on your PR, [update your fork](https://help.github.com/articles/syncing-a-fork/) with the latest code from `master`. -**Note**: We also have some [known intermittent failures](https://github.com/prebid/prebid-server/issues/103). +**Note**: We also have some [known intermittent failures](https://github.com/PubMatic-OpenWrap/prebid-server/issues/103). If the tests still fail after pulling `master`, don't worry about it. We'll re-run them when we review your PR. diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index 6d69aa8241e..fd21be2bd2c 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -36,6 +36,7 @@ var ( errCookieSyncBody = errors.New("Failed to read request body") errCookieSyncGDPRConsentMissing = errors.New("gdpr_consent is required if gdpr=1") errCookieSyncGDPRConsentMissingSignalAmbiguous = errors.New("gdpr_consent is required. gdpr is not specified and is assumed to be 1 by the server. set gdpr=0 to exempt this request") + errCookieSyncGDPRMandatoryByHost = errors.New("gdpr_consent is required. gdpr exemption disabled by host") errCookieSyncInvalidBiddersType = errors.New("invalid bidders type. must either be a string '*' or a string array of bidders") errCookieSyncAccountBlocked = errors.New("account is disabled, please reach out to the prebid server host") errCookieSyncAccountConfigMalformed = errors.New("account config is malformed and could not be read") @@ -141,6 +142,11 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma return usersync.Request{}, macros.UserSyncPrivacy{}, err } + //OpenWrap: do not allow publishers to bypass GDPR + if c.privacyConfig.gdprConfig.DefaultValue == "1" && gdprSignal == gdpr.SignalNo { + return usersync.Request{}, macros.UserSyncPrivacy{}, errCookieSyncGDPRMandatoryByHost + } + ccpaParsedPolicy := ccpa.ParsedPolicy{} if request.USPrivacy != "" { parsedPolicy, err := ccpa.Policy{Consent: request.USPrivacy}.Parse(c.privacyConfig.bidderHashSet) @@ -413,10 +419,16 @@ func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.S } for _, syncerChoice := range s { - syncTypes := tf.ForBidder(syncerChoice.Bidder) + //added hack to support to old wrapper versions having indexExchange as partner + //TODO: Remove when a stable version is released + bidderName := syncerChoice.Bidder + if bidderName == "indexExchange" { + bidderName = "ix" + } + syncTypes := tf.ForBidder(bidderName) sync, err := syncerChoice.Syncer.GetSync(syncTypes, m) if err != nil { - glog.Errorf("Failed to get usersync info for %s: %v", syncerChoice.Bidder, err) + glog.Errorf("Failed to get usersync info for %s: %v", bidderName, err) continue } diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 050e137ffed..f414453adff 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -923,6 +923,13 @@ func TestCookieSyncParseRequest(t *testing.T) { givenCCPAEnabled: true, expectedError: "gdpr_consent is required. gdpr is not specified and is assumed to be 1 by the server. set gdpr=0 to exempt this request", }, + { + description: "Explicit GDPR Signal 0 - Default Value 1", + givenBody: strings.NewReader(`{"gdpr": 0}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "1"}, + givenCCPAEnabled: true, + expectedError: "gdpr_consent is required. gdpr exemption disabled by host", + }, { description: "HTTP Read Error", givenBody: iotest.ErrReader(errors.New("anyError")), diff --git a/endpoints/events/vtrack_ow.go b/endpoints/events/vtrack_ow.go new file mode 100644 index 00000000000..0aba5d14172 --- /dev/null +++ b/endpoints/events/vtrack_ow.go @@ -0,0 +1,284 @@ +package events + +import ( + "encoding/json" + "errors" + "fmt" + "net/url" + "strings" + + "github.com/beevik/etree" + "github.com/golang/glog" + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// standard VAST macros +// https://interactiveadvertisingbureau.github.io/vast/vast4macros/vast4-macros-latest.html#macro-spec-adcount +const ( + VASTAdTypeMacro = "[ADTYPE]" + VASTAppBundleMacro = "[APPBUNDLE]" + VASTDomainMacro = "[DOMAIN]" + VASTPageURLMacro = "[PAGEURL]" + + // PBS specific macros + PBSEventIDMacro = "[EVENT_ID]" // macro for injecting PBS defined video event tracker id + //[PBS-ACCOUNT] represents publisher id / account id + PBSAccountMacro = "[PBS-ACCOUNT]" + // [PBS-BIDDER] represents bidder name + PBSBidderMacro = "[PBS-BIDDER]" + // [PBS-ORIG_BIDID] represents original bid id. + PBSOrigBidIDMacro = "[PBS-ORIG_BIDID]" + // [PBS-BIDID] represents bid id. If auction.generate-bid-id config is on, then resolve with response.seatbid.bid.ext.prebid.bidid. Else replace with response.seatbid.bid.id + PBSBidIDMacro = "[PBS-BIDID]" + // [ADERVERTISER_NAME] represents advertiser name + PBSAdvertiserNameMacro = "[ADVERTISER_NAME]" + // Pass imp.tagId using this macro + PBSAdUnitIDMacro = "[AD_UNIT]" + //PBSBidderCodeMacro represents an alias id or core bidder id. + PBSBidderCodeMacro = "[BIDDER_CODE]" +) + +var trackingEvents = []string{"start", "firstQuartile", "midpoint", "thirdQuartile", "complete"} + +// PubMatic specific event IDs +// This will go in event-config once PreBid modular design is in place +var eventIDMap = map[string]string{ + "start": "2", + "firstQuartile": "4", + "midpoint": "3", + "thirdQuartile": "5", + "complete": "6", +} + +// InjectVideoEventTrackers injects the video tracking events +// Returns VAST xml contains as first argument. Second argument indicates whether the trackers are injected and last argument indicates if there is any error in injecting the trackers +func InjectVideoEventTrackers(trackerURL, vastXML string, bid *openrtb2.Bid, prebidGenBidId, requestingBidder, bidderCoreName, accountID string, timestamp int64, bidRequest *openrtb2.BidRequest) ([]byte, bool, error) { + // parse VAST + doc := etree.NewDocument() + err := doc.ReadFromString(vastXML) + if nil != err { + err = fmt.Errorf("Error parsing VAST XML. '%v'", err.Error()) + glog.Errorf(err.Error()) + return []byte(vastXML), false, err // false indicates events trackers are not injected + } + + //Maintaining BidRequest Impression Map (Copied from exchange.go#applyCategoryMapping) + //TODO: It should be optimized by forming once and reusing + impMap := make(map[string]*openrtb2.Imp) + for i := range bidRequest.Imp { + impMap[bidRequest.Imp[i].ID] = &bidRequest.Imp[i] + } + + eventURLMap := GetVideoEventTracking(trackerURL, bid, prebidGenBidId, requestingBidder, bidderCoreName, accountID, timestamp, bidRequest, doc, impMap) + trackersInjected := false + // return if if no tracking URL + if len(eventURLMap) == 0 { + return []byte(vastXML), false, errors.New("Event URLs are not found") + } + + creatives := FindCreatives(doc) + + if adm := strings.TrimSpace(bid.AdM); adm == "" || strings.HasPrefix(adm, "http") { + // determine which creative type to be created based on linearity + if imp, ok := impMap[bid.ImpID]; ok && nil != imp.Video { + // create creative object + creatives = doc.FindElements("VAST/Ad/Wrapper/Creatives") + // var creative *etree.Element + // if len(creatives) > 0 { + // creative = creatives[0] // consider only first creative + // } else { + creative := doc.CreateElement("Creative") + creatives[0].AddChild(creative) + + // } + + switch imp.Video.Linearity { + case adcom1.LinearityLinear: + creative.AddChild(doc.CreateElement("Linear")) + case adcom1.LinearityNonLinear: + creative.AddChild(doc.CreateElement("NonLinearAds")) + default: // create both type of creatives + creative.AddChild(doc.CreateElement("Linear")) + creative.AddChild(doc.CreateElement("NonLinearAds")) + } + creatives = creative.ChildElements() // point to actual cratives + } + } + for _, creative := range creatives { + trackingEvents := creative.SelectElement("TrackingEvents") + if nil == trackingEvents { + trackingEvents = creative.CreateElement("TrackingEvents") + creative.AddChild(trackingEvents) + } + // Inject + for event, url := range eventURLMap { + trackingEle := trackingEvents.CreateElement("Tracking") + trackingEle.CreateAttr("event", event) + trackingEle.SetText(fmt.Sprintf("%s", url)) + trackersInjected = true + } + } + + out := []byte(vastXML) + var wErr error + if trackersInjected { + out, wErr = doc.WriteToBytes() + trackersInjected = trackersInjected && nil == wErr + if nil != wErr { + glog.Errorf("%v", wErr.Error()) + } + } + return out, trackersInjected, wErr +} + +// GetVideoEventTracking returns map containing key as event name value as associaed video event tracking URL +// By default PBS will expect [EVENT_ID] macro in trackerURL to inject event information +// [EVENT_ID] will be injected with one of the following values +// +// firstQuartile, midpoint, thirdQuartile, complete +// +// If your company can not use [EVENT_ID] and has its own macro. provide config.TrackerMacros implementation +// and ensure that your macro is part of trackerURL configuration +func GetVideoEventTracking(trackerURL string, bid *openrtb2.Bid, prebidGenBidId, requestingBidder string, bidderCoreName string, accountId string, timestamp int64, req *openrtb2.BidRequest, doc *etree.Document, impMap map[string]*openrtb2.Imp) map[string]string { + eventURLMap := make(map[string]string) + if "" == strings.TrimSpace(trackerURL) { + return eventURLMap + } + + // lookup custom macros + var customMacroMap map[string]string + if nil != req.Ext { + reqExt := new(openrtb_ext.ExtRequest) + err := json.Unmarshal(req.Ext, &reqExt) + if err == nil { + customMacroMap = reqExt.Prebid.Macros + } else { + glog.Warningf("Error in unmarshling req.Ext.Prebid.Vast: [%s]", err.Error()) + } + } + + for _, event := range trackingEvents { + eventURL := trackerURL + // lookup in custom macros + if nil != customMacroMap { + for customMacro, value := range customMacroMap { + eventURL = replaceMacro(eventURL, customMacro, value) + } + } + // replace standard macros + eventURL = replaceMacro(eventURL, VASTAdTypeMacro, string(openrtb_ext.BidTypeVideo)) + if nil != req && nil != req.App { + // eventURL = replaceMacro(eventURL, VASTAppBundleMacro, req.App.Bundle) + eventURL = replaceMacro(eventURL, VASTDomainMacro, req.App.Bundle) + if nil != req.App.Publisher { + eventURL = replaceMacro(eventURL, PBSAccountMacro, req.App.Publisher.ID) + } + } + if nil != req && nil != req.Site { + eventURL = replaceMacro(eventURL, VASTDomainMacro, getDomain(req.Site)) + eventURL = replaceMacro(eventURL, VASTPageURLMacro, req.Site.Page) + if nil != req.Site.Publisher { + eventURL = replaceMacro(eventURL, PBSAccountMacro, req.Site.Publisher.ID) + } + } + + domain := "" + if len(bid.ADomain) > 0 { + var err error + //eventURL = replaceMacro(eventURL, PBSAdvertiserNameMacro, strings.Join(bid.ADomain, ",")) + domain, err = extractDomain(bid.ADomain[0]) + if err != nil { + glog.Warningf("Unable to extract domain from '%s'. [%s]", bid.ADomain[0], err.Error()) + } + } + + eventURL = replaceMacro(eventURL, PBSAdvertiserNameMacro, domain) + + eventURL = replaceMacro(eventURL, PBSBidderMacro, bidderCoreName) + eventURL = replaceMacro(eventURL, PBSBidderCodeMacro, requestingBidder) + + /* Use generated bidId if present, else use bid.ID */ + if len(prebidGenBidId) > 0 && prebidGenBidId != bid.ID { + eventURL = replaceMacro(eventURL, PBSBidIDMacro, prebidGenBidId) + } else { + eventURL = replaceMacro(eventURL, PBSBidIDMacro, bid.ID) + } + eventURL = replaceMacro(eventURL, PBSOrigBidIDMacro, bid.ID) + + // replace [EVENT_ID] macro with PBS defined event ID + eventURL = replaceMacro(eventURL, PBSEventIDMacro, eventIDMap[event]) + + if imp, ok := impMap[bid.ImpID]; ok { + eventURL = replaceMacro(eventURL, PBSAdUnitIDMacro, imp.TagID) + } else { + glog.Warningf("Setting empty value for %s macro, as failed to determine imp.TagID for bid.ImpID: %s", PBSAdUnitIDMacro, bid.ImpID) + eventURL = replaceMacro(eventURL, PBSAdUnitIDMacro, "") + } + + eventURLMap[event] = eventURL + } + return eventURLMap +} + +func replaceMacro(trackerURL, macro, value string) string { + macro = strings.TrimSpace(macro) + trimmedValue := strings.TrimSpace(value) + + if strings.HasPrefix(macro, "[") && strings.HasSuffix(macro, "]") && len(trimmedValue) > 0 { + trackerURL = strings.ReplaceAll(trackerURL, macro, url.QueryEscape(value)) + } else if strings.HasPrefix(macro, "[") && strings.HasSuffix(macro, "]") && len(trimmedValue) == 0 { + trackerURL = strings.ReplaceAll(trackerURL, macro, url.QueryEscape("")) + } else { + glog.Warningf("Invalid macro '%v'. Either empty or missing prefix '[' or suffix ']", macro) + } + return trackerURL +} + +// FindCreatives finds Linear, NonLinearAds fro InLine and Wrapper Type of creatives +// from input doc - VAST Document +// NOTE: This function is temporarily seperated to reuse in ctv_auction.go. Because, in case of ctv +// we generate bid.id +func FindCreatives(doc *etree.Document) []*etree.Element { + // Find Creatives of Linear and NonLinear Type + // Injecting Tracking Events for Companion is not supported here + creatives := doc.FindElements("VAST/Ad/InLine/Creatives/Creative/Linear") + creatives = append(creatives, doc.FindElements("VAST/Ad/Wrapper/Creatives/Creative/Linear")...) + creatives = append(creatives, doc.FindElements("VAST/Ad/InLine/Creatives/Creative/NonLinearAds")...) + creatives = append(creatives, doc.FindElements("VAST/Ad/Wrapper/Creatives/Creative/NonLinearAds")...) + return creatives +} + +func extractDomain(rawURL string) (string, error) { + if !strings.HasPrefix(rawURL, "http") { + rawURL = "http://" + rawURL + } + // decode rawURL + rawURL, err := url.QueryUnescape(rawURL) + if nil != err { + return "", err + } + url, err := url.Parse(rawURL) + if nil != err { + return "", err + } + // remove www if present + return strings.TrimPrefix(url.Hostname(), "www."), nil +} + +func getDomain(site *openrtb2.Site) string { + if site.Domain != "" { + return site.Domain + } + + hostname := "" + + if site.Page != "" { + pageURL, err := url.Parse(site.Page) + if err == nil && pageURL != nil { + hostname = pageURL.Host + } + } + return hostname +} diff --git a/endpoints/events/vtrack_ow_test.go b/endpoints/events/vtrack_ow_test.go new file mode 100644 index 00000000000..33177e66eb1 --- /dev/null +++ b/endpoints/events/vtrack_ow_test.go @@ -0,0 +1,636 @@ +package events + +import ( + "fmt" + "net/url" + "testing" + + "github.com/beevik/etree" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/stretchr/testify/assert" +) + +func TestInjectVideoEventTrackers(t *testing.T) { + type args struct { + externalURL string + genbidID string + bid *openrtb2.Bid + req *openrtb2.BidRequest + } + type want struct { + eventURLs map[string][]string + } + tests := []struct { + name string + args args + want want + }{ + { + name: "linear_creative", + args: args{ + externalURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]", + bid: &openrtb2.Bid{ + AdM: ` + + + + http://example.com/tracking/midpoint + http://example.com/tracking/thirdQuartile + http://example.com/tracking/complete + http://partner.tracking.url + + + `, + }, + req: &openrtb2.BidRequest{App: &openrtb2.App{Bundle: "abc"}}, + }, + want: want{ + eventURLs: map[string][]string{ + // "firstQuartile": {"http://example.com/tracking/firstQuartile?k1=v1&k2=v2", "http://company.tracker.com?eventId=1004&appbundle=abc"}, + // "midpoint": {"http://example.com/tracking/midpoint", "http://company.tracker.com?eventId=1003&appbundle=abc"}, + // "thirdQuartile": {"http://example.com/tracking/thirdQuartile", "http://company.tracker.com?eventId=1005&appbundle=abc"}, + // "complete": {"http://example.com/tracking/complete", "http://company.tracker.com?eventId=1006&appbundle=abc"}, + "firstQuartile": {"http://example.com/tracking/firstQuartile?k1=v1&k2=v2", "http://company.tracker.com?eventId=4&appbundle=abc"}, + "midpoint": {"http://example.com/tracking/midpoint", "http://company.tracker.com?eventId=3&appbundle=abc"}, + "thirdQuartile": {"http://example.com/tracking/thirdQuartile", "http://company.tracker.com?eventId=5&appbundle=abc"}, + "complete": {"http://example.com/tracking/complete", "http://company.tracker.com?eventId=6&appbundle=abc"}, + "start": {"http://company.tracker.com?eventId=2&appbundle=abc", "http://partner.tracking.url"}, + }, + }, + }, + { + name: "non_linear_creative", + args: args{ + externalURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]", + bid: &openrtb2.Bid{ // Adm contains to TrackingEvents tag + AdM: ` + + + http://something.com + + + `, + }, + req: &openrtb2.BidRequest{App: &openrtb2.App{Bundle: "abc"}}, + }, + want: want{ + eventURLs: map[string][]string{ + // "firstQuartile": {"http://something.com", "http://company.tracker.com?eventId=1004&appbundle=abc"}, + // "midpoint": {"http://company.tracker.com?eventId=1003&appbundle=abc"}, + // "thirdQuartile": {"http://company.tracker.com?eventId=1005&appbundle=abc"}, + // "complete": {"http://company.tracker.com?eventId=1006&appbundle=abc"}, + "firstQuartile": {"http://something.com", "http://company.tracker.com?eventId=4&appbundle=abc"}, + "midpoint": {"http://company.tracker.com?eventId=3&appbundle=abc"}, + "thirdQuartile": {"http://company.tracker.com?eventId=5&appbundle=abc"}, + "complete": {"http://company.tracker.com?eventId=6&appbundle=abc"}, + "start": {"http://company.tracker.com?eventId=2&appbundle=abc"}, + }, + }, + }, { + name: "no_traker_url_configured", // expect no injection + args: args{ + externalURL: "", + bid: &openrtb2.Bid{ // Adm contains to TrackingEvents tag + AdM: ` + + + `, + }, + req: &openrtb2.BidRequest{App: &openrtb2.App{Bundle: "abc"}}, + }, + want: want{ + eventURLs: map[string][]string{}, + }, + }, + { + name: "wrapper_vast_xml_from_partner", // expect we are injecting trackers inside wrapper + args: args{ + externalURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]", + bid: &openrtb2.Bid{ // Adm contains to TrackingEvents tag + AdM: ` + + + iabtechlab + http://somevasturl + + + + + + `, + }, + req: &openrtb2.BidRequest{App: &openrtb2.App{Bundle: "abc"}}, + }, + want: want{ + eventURLs: map[string][]string{ + // "firstQuartile": {"http://company.tracker.com?eventId=firstQuartile&appbundle=abc"}, + // "midpoint": {"http://company.tracker.com?eventId=midpoint&appbundle=abc"}, + // "thirdQuartile": {"http://company.tracker.com?eventId=thirdQuartile&appbundle=abc"}, + // "complete": {"http://company.tracker.com?eventId=complete&appbundle=abc"}, + "firstQuartile": {"http://company.tracker.com?eventId=4&appbundle=abc"}, + "midpoint": {"http://company.tracker.com?eventId=3&appbundle=abc"}, + "thirdQuartile": {"http://company.tracker.com?eventId=5&appbundle=abc"}, + "complete": {"http://company.tracker.com?eventId=6&appbundle=abc"}, + "start": {"http://company.tracker.com?eventId=2&appbundle=abc"}, + }, + }, + }, + // { + // name: "vast_tag_uri_response_from_partner", + // args: args{ + // externalURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]", + // bid: &openrtb2.Bid{ // Adm contains to TrackingEvents tag + // AdM: ``, + // }, + // req: &openrtb2.BidRequest{App: &openrtb2.App{Bundle: "abc"}}, + // }, + // want: want{ + // eventURLs: map[string][]string{ + // "firstQuartile": {"http://company.tracker.com?eventId=firstQuartile&appbundle=abc"}, + // "midpoint": {"http://company.tracker.com?eventId=midpoint&appbundle=abc"}, + // "thirdQuartile": {"http://company.tracker.com?eventId=thirdQuartile&appbundle=abc"}, + // "complete": {"http://company.tracker.com?eventId=complete&appbundle=abc"}, + // }, + // }, + // }, + // { + // name: "adm_empty", + // args: args{ + // externalURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]", + // bid: &openrtb2.Bid{ // Adm contains to TrackingEvents tag + // AdM: "", + // NURL: "nurl_contents", + // }, + // req: &openrtb2.BidRequest{App: &openrtb2.App{Bundle: "abc"}}, + // }, + // want: want{ + // eventURLs: map[string][]string{ + // "firstQuartile": {"http://company.tracker.com?eventId=firstQuartile&appbundle=abc"}, + // "midpoint": {"http://company.tracker.com?eventId=midpoint&appbundle=abc"}, + // "thirdQuartile": {"http://company.tracker.com?eventId=thirdQuartile&appbundle=abc"}, + // "complete": {"http://company.tracker.com?eventId=complete&appbundle=abc"}, + // }, + // }, + // }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + vast := "" + if nil != tc.args.bid { + vast = tc.args.bid.AdM // original vast + } + // bind this bid id with imp object + tc.args.req.Imp = []openrtb2.Imp{{ID: "123", Video: &openrtb2.Video{}}} + tc.args.bid.ImpID = tc.args.req.Imp[0].ID + accountID := "" + timestamp := int64(0) + requestingBidder := "test_bidder" + bidderCoreName := "test_core_bidder" + injectedVast, injected, ierr := InjectVideoEventTrackers(tc.args.externalURL, vast, tc.args.bid, tc.args.genbidID, requestingBidder, bidderCoreName, accountID, timestamp, tc.args.req) + + if !injected { + // expect no change in input vast if tracking events are not injected + assert.Equal(t, vast, string(injectedVast)) + assert.NotNil(t, ierr) + } else { + assert.Nil(t, ierr) + } + actualVastDoc := etree.NewDocument() + + err := actualVastDoc.ReadFromBytes(injectedVast) + if nil != err { + assert.Fail(t, err.Error()) + } + + // fmt.Println(string(injectedVast)) + actualTrackingEvents := actualVastDoc.FindElements("VAST/Ad/InLine/Creatives/Creative/Linear/TrackingEvents/Tracking") + actualTrackingEvents = append(actualTrackingEvents, actualVastDoc.FindElements("VAST/Ad/InLine/Creatives/Creative/NonLinearAds/TrackingEvents/Tracking")...) + actualTrackingEvents = append(actualTrackingEvents, actualVastDoc.FindElements("VAST/Ad/Wrapper/Creatives/Creative/Linear/TrackingEvents/Tracking")...) + actualTrackingEvents = append(actualTrackingEvents, actualVastDoc.FindElements("VAST/Ad/Wrapper/Creatives/Creative/NonLinearAds/TrackingEvents/Tracking")...) + + totalURLCount := 0 + for event, URLs := range tc.want.eventURLs { + + for _, expectedURL := range URLs { + present := false + for _, te := range actualTrackingEvents { + if te.SelectAttr("event").Value == event && te.Text() == expectedURL { + present = true + totalURLCount++ + break // expected URL present. check for next expected URL + } + } + if !present { + assert.Fail(t, "Expected tracker URL '"+expectedURL+"' is not present") + } + } + } + // ensure all total of events are injected + assert.Equal(t, totalURLCount, len(actualTrackingEvents), fmt.Sprintf("Expected '%v' event trackers. But found '%v'", len(tc.want.eventURLs), len(actualTrackingEvents))) + + }) + } +} + +func TestGetVideoEventTracking(t *testing.T) { + type args struct { + trackerURL string + bid *openrtb2.Bid + requestingBidder string + gen_bidid string + bidderCoreName string + accountId string + timestamp int64 + req *openrtb2.BidRequest + doc *etree.Document + } + type want struct { + trackerURLMap map[string]string + } + tests := []struct { + name string + args args + want want + }{ + { + name: "valid_scenario", + args: args{ + trackerURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]", + bid: &openrtb2.Bid{ + // AdM: vastXMLWith2Creatives, + }, + req: &openrtb2.BidRequest{ + App: &openrtb2.App{ + Bundle: "someappbundle", + }, + Imp: []openrtb2.Imp{}, + }, + }, + want: want{ + trackerURLMap: map[string]string{ + // "firstQuartile": "http://company.tracker.com?eventId=firstQuartile&appbundle=someappbundle", + // "midpoint": "http://company.tracker.com?eventId=midpoint&appbundle=someappbundle", + // "thirdQuartile": "http://company.tracker.com?eventId=thirdQuartile&appbundle=someappbundle", + // "complete": "http://company.tracker.com?eventId=complete&appbundle=someappbundle"}, + "firstQuartile": "http://company.tracker.com?eventId=4&appbundle=someappbundle", + "midpoint": "http://company.tracker.com?eventId=3&appbundle=someappbundle", + "thirdQuartile": "http://company.tracker.com?eventId=5&appbundle=someappbundle", + "start": "http://company.tracker.com?eventId=2&appbundle=someappbundle", + "complete": "http://company.tracker.com?eventId=6&appbundle=someappbundle"}, + }, + }, + { + name: "no_macro_value", // expect no replacement + args: args{ + trackerURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]", + bid: &openrtb2.Bid{}, + req: &openrtb2.BidRequest{ + App: &openrtb2.App{}, // no app bundle value + Imp: []openrtb2.Imp{}, + }, + }, + want: want{ + trackerURLMap: map[string]string{ + // "firstQuartile": "http://company.tracker.com?eventId=firstQuartile&appbundle=[DOMAIN]", + // "midpoint": "http://company.tracker.com?eventId=midpoint&appbundle=[DOMAIN]", + // "thirdQuartile": "http://company.tracker.com?eventId=thirdQuartile&appbundle=[DOMAIN]", + // "complete": "http://company.tracker.com?eventId=complete&appbundle=[DOMAIN]"}, + "firstQuartile": "http://company.tracker.com?eventId=4&appbundle=", + "midpoint": "http://company.tracker.com?eventId=3&appbundle=", + "thirdQuartile": "http://company.tracker.com?eventId=5&appbundle=", + "start": "http://company.tracker.com?eventId=2&appbundle=", + "complete": "http://company.tracker.com?eventId=6&appbundle="}, + }, + }, + { + name: "prefer_company_value_for_standard_macro", + args: args{ + trackerURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]", + req: &openrtb2.BidRequest{ + App: &openrtb2.App{ + Bundle: "myapp", // do not expect this value + }, + Imp: []openrtb2.Imp{}, + Ext: []byte(`{"prebid":{ + "macros": { + "[DOMAIN]": "my_custom_value" + } + }}`), + }, + }, + want: want{ + trackerURLMap: map[string]string{ + // "firstQuartile": "http://company.tracker.com?eventId=firstQuartile&appbundle=my_custom_value", + // "midpoint": "http://company.tracker.com?eventId=midpoint&appbundle=my_custom_value", + // "thirdQuartile": "http://company.tracker.com?eventId=thirdQuartile&appbundle=my_custom_value", + // "complete": "http://company.tracker.com?eventId=complete&appbundle=my_custom_value"}, + "firstQuartile": "http://company.tracker.com?eventId=4&appbundle=my_custom_value", + "midpoint": "http://company.tracker.com?eventId=3&appbundle=my_custom_value", + "thirdQuartile": "http://company.tracker.com?eventId=5&appbundle=my_custom_value", + "start": "http://company.tracker.com?eventId=2&appbundle=my_custom_value", + "complete": "http://company.tracker.com?eventId=6&appbundle=my_custom_value"}, + }, + }, { + name: "multireplace_macro", + args: args{ + trackerURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]¶meter2=[DOMAIN]", + req: &openrtb2.BidRequest{ + App: &openrtb2.App{ + Bundle: "myapp123", + }, + Imp: []openrtb2.Imp{}, + }, + }, + want: want{ + trackerURLMap: map[string]string{ + // "firstQuartile": "http://company.tracker.com?eventId=firstQuartile&appbundle=myapp123¶meter2=myapp123", + // "midpoint": "http://company.tracker.com?eventId=midpoint&appbundle=myapp123¶meter2=myapp123", + // "thirdQuartile": "http://company.tracker.com?eventId=thirdQuartile&appbundle=myapp123¶meter2=myapp123", + // "complete": "http://company.tracker.com?eventId=complete&appbundle=myapp123¶meter2=myapp123"}, + "firstQuartile": "http://company.tracker.com?eventId=4&appbundle=myapp123¶meter2=myapp123", + "midpoint": "http://company.tracker.com?eventId=3&appbundle=myapp123¶meter2=myapp123", + "thirdQuartile": "http://company.tracker.com?eventId=5&appbundle=myapp123¶meter2=myapp123", + "start": "http://company.tracker.com?eventId=2&appbundle=myapp123¶meter2=myapp123", + "complete": "http://company.tracker.com?eventId=6&appbundle=myapp123¶meter2=myapp123"}, + }, + }, + { + name: "custom_macro_without_prefix_and_suffix", + args: args{ + trackerURL: "http://company.tracker.com?eventId=[EVENT_ID]¶m1=[CUSTOM_MACRO]", + req: &openrtb2.BidRequest{ + Ext: []byte(`{"prebid":{ + "macros": { + "CUSTOM_MACRO": "my_custom_value" + } + }}`), + Imp: []openrtb2.Imp{}, + }, + }, + want: want{ + trackerURLMap: map[string]string{ + // "firstQuartile": "http://company.tracker.com?eventId=firstQuartile¶m1=[CUSTOM_MACRO]", + // "midpoint": "http://company.tracker.com?eventId=midpoint¶m1=[CUSTOM_MACRO]", + // "thirdQuartile": "http://company.tracker.com?eventId=thirdQuartile¶m1=[CUSTOM_MACRO]", + // "complete": "http://company.tracker.com?eventId=complete¶m1=[CUSTOM_MACRO]"}, + "firstQuartile": "http://company.tracker.com?eventId=4¶m1=[CUSTOM_MACRO]", + "midpoint": "http://company.tracker.com?eventId=3¶m1=[CUSTOM_MACRO]", + "thirdQuartile": "http://company.tracker.com?eventId=5¶m1=[CUSTOM_MACRO]", + "start": "http://company.tracker.com?eventId=2¶m1=[CUSTOM_MACRO]", + "complete": "http://company.tracker.com?eventId=6¶m1=[CUSTOM_MACRO]"}, + }, + }, + { + name: "empty_macro", + args: args{ + trackerURL: "http://company.tracker.com?eventId=[EVENT_ID]¶m1=[CUSTOM_MACRO]", + req: &openrtb2.BidRequest{ + Ext: []byte(`{"prebid":{ + "macros": { + "": "my_custom_value" + } + }}`), + Imp: []openrtb2.Imp{}, + }, + }, + want: want{ + trackerURLMap: map[string]string{ + // "firstQuartile": "http://company.tracker.com?eventId=firstQuartile¶m1=[CUSTOM_MACRO]", + // "midpoint": "http://company.tracker.com?eventId=midpoint¶m1=[CUSTOM_MACRO]", + // "thirdQuartile": "http://company.tracker.com?eventId=thirdQuartile¶m1=[CUSTOM_MACRO]", + // "complete": "http://company.tracker.com?eventId=complete¶m1=[CUSTOM_MACRO]"}, + "firstQuartile": "http://company.tracker.com?eventId=4¶m1=[CUSTOM_MACRO]", + "midpoint": "http://company.tracker.com?eventId=3¶m1=[CUSTOM_MACRO]", + "thirdQuartile": "http://company.tracker.com?eventId=5¶m1=[CUSTOM_MACRO]", + "start": "http://company.tracker.com?eventId=2¶m1=[CUSTOM_MACRO]", + "complete": "http://company.tracker.com?eventId=6¶m1=[CUSTOM_MACRO]"}, + }, + }, + { + name: "macro_is_case_sensitive", + args: args{ + trackerURL: "http://company.tracker.com?eventId=[EVENT_ID]¶m1=[CUSTOM_MACRO]", + req: &openrtb2.BidRequest{ + Ext: []byte(`{"prebid":{ + "macros": { + "": "my_custom_value" + } + }}`), + Imp: []openrtb2.Imp{}, + }, + }, + want: want{ + trackerURLMap: map[string]string{ + // "firstQuartile": "http://company.tracker.com?eventId=firstQuartile¶m1=[CUSTOM_MACRO]", + // "midpoint": "http://company.tracker.com?eventId=midpoint¶m1=[CUSTOM_MACRO]", + // "thirdQuartile": "http://company.tracker.com?eventId=thirdQuartile¶m1=[CUSTOM_MACRO]", + // "complete": "http://company.tracker.com?eventId=complete¶m1=[CUSTOM_MACRO]"}, + "firstQuartile": "http://company.tracker.com?eventId=4¶m1=[CUSTOM_MACRO]", + "midpoint": "http://company.tracker.com?eventId=3¶m1=[CUSTOM_MACRO]", + "thirdQuartile": "http://company.tracker.com?eventId=5¶m1=[CUSTOM_MACRO]", + "start": "http://company.tracker.com?eventId=2¶m1=[CUSTOM_MACRO]", + "complete": "http://company.tracker.com?eventId=6¶m1=[CUSTOM_MACRO]"}, + }, + }, + { + name: "empty_tracker_url", + args: args{trackerURL: " ", req: &openrtb2.BidRequest{Imp: []openrtb2.Imp{}}}, + want: want{trackerURLMap: make(map[string]string)}, + }, + { + name: "site_domain_tracker_url", + args: args{trackerURL: "https://company.tracker.com?operId=8&e=[EVENT_ID]&p=[PBS-ACCOUNT]&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=[PBS-BIDDER]&advertiser_id=[ADVERTISER_NAME]&sURL=[DOMAIN]&pfi=[PLATFORM]&af=[ADTYPE]&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=[AD_UNIT]&bidid=[PBS-BIDID]", + req: &openrtb2.BidRequest{Site: &openrtb2.Site{Name: "test", Domain: "www.test.com", Publisher: &openrtb2.Publisher{ID: "5890"}}, Imp: []openrtb2.Imp{}}}, + want: want{ + map[string]string{ + "complete": "https://company.tracker.com?operId=8&e=6&p=5890&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=&advertiser_id=&sURL=www.test.com&pfi=[PLATFORM]&af=video&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=&bidid=", + "firstQuartile": "https://company.tracker.com?operId=8&e=4&p=5890&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=&advertiser_id=&sURL=www.test.com&pfi=[PLATFORM]&af=video&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=&bidid=", + "midpoint": "https://company.tracker.com?operId=8&e=3&p=5890&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=&advertiser_id=&sURL=www.test.com&pfi=[PLATFORM]&af=video&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=&bidid=", + "start": "https://company.tracker.com?operId=8&e=2&p=5890&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=&advertiser_id=&sURL=www.test.com&pfi=[PLATFORM]&af=video&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=&bidid=", + "thirdQuartile": "https://company.tracker.com?operId=8&e=5&p=5890&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=&advertiser_id=&sURL=www.test.com&pfi=[PLATFORM]&af=video&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=&bidid=", + }, + }, + }, + { + name: "site_page_tracker_url", + args: args{trackerURL: "https://company.tracker.com?operId=8&e=[EVENT_ID]&p=[PBS-ACCOUNT]&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=[PBS-BIDDER]&advertiser_id=[ADVERTISER_NAME]&sURL=[DOMAIN]&pfi=[PLATFORM]&af=[ADTYPE]&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=[AD_UNIT]&bidid=[PBS-BIDID]", + req: &openrtb2.BidRequest{Site: &openrtb2.Site{Name: "test", Page: "https://www.test.com/", Publisher: &openrtb2.Publisher{ID: "5890"}}, Imp: []openrtb2.Imp{}}}, + want: want{ + map[string]string{ + "complete": "https://company.tracker.com?operId=8&e=6&p=5890&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=&advertiser_id=&sURL=www.test.com&pfi=[PLATFORM]&af=video&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=&bidid=", + "firstQuartile": "https://company.tracker.com?operId=8&e=4&p=5890&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=&advertiser_id=&sURL=www.test.com&pfi=[PLATFORM]&af=video&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=&bidid=", + "midpoint": "https://company.tracker.com?operId=8&e=3&p=5890&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=&advertiser_id=&sURL=www.test.com&pfi=[PLATFORM]&af=video&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=&bidid=", + "start": "https://company.tracker.com?operId=8&e=2&p=5890&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=&advertiser_id=&sURL=www.test.com&pfi=[PLATFORM]&af=video&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=&bidid=", + "thirdQuartile": "https://company.tracker.com?operId=8&e=5&p=5890&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=&advertiser_id=&sURL=www.test.com&pfi=[PLATFORM]&af=video&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=&bidid=", + }, + }, + }, + { + name: "all_macros with generated_bidId", // expect encoding for WRAPPER_IMPRESSION_ID macro + args: args{ + trackerURL: "https://company.tracker.com?operId=8&e=[EVENT_ID]&p=[PBS-ACCOUNT]&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=[PBS-BIDDER]&advertiser_id=[ADVERTISER_NAME]&sURL=[DOMAIN]&pfi=[PLATFORM]&af=[ADTYPE]&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=[AD_UNIT]&bidid=[PBS-BIDID]&origbidid=[PBS-ORIG_BIDID]&bc=[BIDDER_CODE]", + req: &openrtb2.BidRequest{ + App: &openrtb2.App{Bundle: "com.someapp.com", Publisher: &openrtb2.Publisher{ID: "5890"}}, + Ext: []byte(`{ + "prebid": { + "macros": { + "[PROFILE_ID]": "100", + "[PROFILE_VERSION]": "2", + "[UNIX_TIMESTAMP]": "1234567890", + "[PLATFORM]": "7", + "[WRAPPER_IMPRESSION_ID]": "abc~!@#$%^&&*()_+{}|:\"<>?[]\\;',./" + } + } + }`), + Imp: []openrtb2.Imp{ + {TagID: "/testadunit/1", ID: "imp_1"}, + }, + }, + bid: &openrtb2.Bid{ADomain: []string{"http://a.com/32?k=v", "b.com"}, ImpID: "imp_1", ID: "test_bid_id"}, + gen_bidid: "random_bid_id", + requestingBidder: "test_bidder:234", + bidderCoreName: "test_core_bidder:234", + }, + want: want{ + trackerURLMap: map[string]string{ + "firstQuartile": "https://company.tracker.com?operId=8&e=4&p=5890&pid=100&v=2&ts=1234567890&pn=test_core_bidder%3A234&advertiser_id=a.com&sURL=com.someapp.com&pfi=7&af=video&iid=abc~%21%40%23%24%25%5E%26%26%2A%28%29_%2B%7B%7D%7C%3A%22%3C%3E%3F%5B%5D%5C%3B%27%2C.%2F&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=%2Ftestadunit%2F1&bidid=random_bid_id&origbidid=test_bid_id&bc=test_bidder%3A234", + "midpoint": "https://company.tracker.com?operId=8&e=3&p=5890&pid=100&v=2&ts=1234567890&pn=test_core_bidder%3A234&advertiser_id=a.com&sURL=com.someapp.com&pfi=7&af=video&iid=abc~%21%40%23%24%25%5E%26%26%2A%28%29_%2B%7B%7D%7C%3A%22%3C%3E%3F%5B%5D%5C%3B%27%2C.%2F&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=%2Ftestadunit%2F1&bidid=random_bid_id&origbidid=test_bid_id&bc=test_bidder%3A234", + "thirdQuartile": "https://company.tracker.com?operId=8&e=5&p=5890&pid=100&v=2&ts=1234567890&pn=test_core_bidder%3A234&advertiser_id=a.com&sURL=com.someapp.com&pfi=7&af=video&iid=abc~%21%40%23%24%25%5E%26%26%2A%28%29_%2B%7B%7D%7C%3A%22%3C%3E%3F%5B%5D%5C%3B%27%2C.%2F&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=%2Ftestadunit%2F1&bidid=random_bid_id&origbidid=test_bid_id&bc=test_bidder%3A234", + "complete": "https://company.tracker.com?operId=8&e=6&p=5890&pid=100&v=2&ts=1234567890&pn=test_core_bidder%3A234&advertiser_id=a.com&sURL=com.someapp.com&pfi=7&af=video&iid=abc~%21%40%23%24%25%5E%26%26%2A%28%29_%2B%7B%7D%7C%3A%22%3C%3E%3F%5B%5D%5C%3B%27%2C.%2F&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=%2Ftestadunit%2F1&bidid=random_bid_id&origbidid=test_bid_id&bc=test_bidder%3A234", + "start": "https://company.tracker.com?operId=8&e=2&p=5890&pid=100&v=2&ts=1234567890&pn=test_core_bidder%3A234&advertiser_id=a.com&sURL=com.someapp.com&pfi=7&af=video&iid=abc~%21%40%23%24%25%5E%26%26%2A%28%29_%2B%7B%7D%7C%3A%22%3C%3E%3F%5B%5D%5C%3B%27%2C.%2F&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=%2Ftestadunit%2F1&bidid=random_bid_id&origbidid=test_bid_id&bc=test_bidder%3A234"}, + }, + }, + { + name: "all_macros with empty generated_bidId", // expect encoding for WRAPPER_IMPRESSION_ID macro + args: args{ + trackerURL: "https://company.tracker.com?operId=8&e=[EVENT_ID]&p=[PBS-ACCOUNT]&pid=[PROFILE_ID]&v=[PROFILE_VERSION]&ts=[UNIX_TIMESTAMP]&pn=[PBS-BIDDER]&advertiser_id=[ADVERTISER_NAME]&sURL=[DOMAIN]&pfi=[PLATFORM]&af=[ADTYPE]&iid=[WRAPPER_IMPRESSION_ID]&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=[AD_UNIT]&bidid=[PBS-BIDID]&origbidid=[PBS-ORIG_BIDID]&bc=[BIDDER_CODE]", + req: &openrtb2.BidRequest{ + App: &openrtb2.App{Bundle: "com.someapp.com", Publisher: &openrtb2.Publisher{ID: "5890"}}, + Ext: []byte(`{ + "prebid": { + "macros": { + "[PROFILE_ID]": "100", + "[PROFILE_VERSION]": "2", + "[UNIX_TIMESTAMP]": "1234567890", + "[PLATFORM]": "7", + "[WRAPPER_IMPRESSION_ID]": "abc~!@#$%^&&*()_+{}|:\"<>?[]\\;',./" + } + } + }`), + Imp: []openrtb2.Imp{ + {TagID: "/testadunit/1", ID: "imp_1"}, + }, + }, + bid: &openrtb2.Bid{ADomain: []string{"http://a.com/32?k=v", "b.com"}, ImpID: "imp_1", ID: "test_bid_id"}, + gen_bidid: "", + requestingBidder: "test_bidder:234", + bidderCoreName: "test_core_bidder:234", + }, + want: want{ + trackerURLMap: map[string]string{ + "firstQuartile": "https://company.tracker.com?operId=8&e=4&p=5890&pid=100&v=2&ts=1234567890&pn=test_core_bidder%3A234&advertiser_id=a.com&sURL=com.someapp.com&pfi=7&af=video&iid=abc~%21%40%23%24%25%5E%26%26%2A%28%29_%2B%7B%7D%7C%3A%22%3C%3E%3F%5B%5D%5C%3B%27%2C.%2F&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=%2Ftestadunit%2F1&bidid=test_bid_id&origbidid=test_bid_id&bc=test_bidder%3A234", + "midpoint": "https://company.tracker.com?operId=8&e=3&p=5890&pid=100&v=2&ts=1234567890&pn=test_core_bidder%3A234&advertiser_id=a.com&sURL=com.someapp.com&pfi=7&af=video&iid=abc~%21%40%23%24%25%5E%26%26%2A%28%29_%2B%7B%7D%7C%3A%22%3C%3E%3F%5B%5D%5C%3B%27%2C.%2F&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=%2Ftestadunit%2F1&bidid=test_bid_id&origbidid=test_bid_id&bc=test_bidder%3A234", + "thirdQuartile": "https://company.tracker.com?operId=8&e=5&p=5890&pid=100&v=2&ts=1234567890&pn=test_core_bidder%3A234&advertiser_id=a.com&sURL=com.someapp.com&pfi=7&af=video&iid=abc~%21%40%23%24%25%5E%26%26%2A%28%29_%2B%7B%7D%7C%3A%22%3C%3E%3F%5B%5D%5C%3B%27%2C.%2F&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=%2Ftestadunit%2F1&bidid=test_bid_id&origbidid=test_bid_id&bc=test_bidder%3A234", + "complete": "https://company.tracker.com?operId=8&e=6&p=5890&pid=100&v=2&ts=1234567890&pn=test_core_bidder%3A234&advertiser_id=a.com&sURL=com.someapp.com&pfi=7&af=video&iid=abc~%21%40%23%24%25%5E%26%26%2A%28%29_%2B%7B%7D%7C%3A%22%3C%3E%3F%5B%5D%5C%3B%27%2C.%2F&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=%2Ftestadunit%2F1&bidid=test_bid_id&origbidid=test_bid_id&bc=test_bidder%3A234", + "start": "https://company.tracker.com?operId=8&e=2&p=5890&pid=100&v=2&ts=1234567890&pn=test_core_bidder%3A234&advertiser_id=a.com&sURL=com.someapp.com&pfi=7&af=video&iid=abc~%21%40%23%24%25%5E%26%26%2A%28%29_%2B%7B%7D%7C%3A%22%3C%3E%3F%5B%5D%5C%3B%27%2C.%2F&pseq=[PODSEQUENCE]&adcnt=[ADCOUNT]&cb=[CACHEBUSTING]&au=%2Ftestadunit%2F1&bidid=test_bid_id&origbidid=test_bid_id&bc=test_bidder%3A234"}, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + + if nil == tc.args.bid { + tc.args.bid = &openrtb2.Bid{} + } + + impMap := map[string]*openrtb2.Imp{} + + for _, imp := range tc.args.req.Imp { + impMap[imp.ID] = &imp + } + + eventURLMap := GetVideoEventTracking(tc.args.trackerURL, tc.args.bid, tc.args.gen_bidid, tc.args.requestingBidder, tc.args.bidderCoreName, tc.args.accountId, tc.args.timestamp, tc.args.req, tc.args.doc, impMap) + + for event, eurl := range tc.want.trackerURLMap { + + u, _ := url.Parse(eurl) + expectedValues, _ := url.ParseQuery(u.RawQuery) + u, _ = url.Parse(eventURLMap[event]) + actualValues, _ := url.ParseQuery(u.RawQuery) + for k, ev := range expectedValues { + av := actualValues[k] + for i := 0; i < len(ev); i++ { + assert.Equal(t, ev[i], av[i], fmt.Sprintf("Expected '%v' for '%v'. but found %v", ev[i], k, av[i])) + } + } + + // error out if extra query params + if len(expectedValues) != len(actualValues) { + assert.Equal(t, expectedValues, actualValues, fmt.Sprintf("Expected '%v' query params but found '%v'", len(expectedValues), len(actualValues))) + break + } + } + + // check if new quartile pixels are covered inside test + assert.Equal(t, tc.want.trackerURLMap, eventURLMap) + }) + } +} + +func TestReplaceMacro(t *testing.T) { + type args struct { + trackerURL string + macro string + value string + } + type want struct { + trackerURL string + } + tests := []struct { + name string + args args + want want + }{ + {name: "empty_tracker_url", args: args{trackerURL: "", macro: "[TEST]", value: "testme"}, want: want{trackerURL: ""}}, + {name: "tracker_url_with_macro", args: args{trackerURL: "http://something.com?test=[TEST]", macro: "[TEST]", value: "testme"}, want: want{trackerURL: "http://something.com?test=testme"}}, + {name: "tracker_url_with_invalid_macro", args: args{trackerURL: "http://something.com?test=TEST]", macro: "[TEST]", value: "testme"}, want: want{trackerURL: "http://something.com?test=TEST]"}}, + {name: "tracker_url_with_repeating_macro", args: args{trackerURL: "http://something.com?test=[TEST]&test1=[TEST]", macro: "[TEST]", value: "testme"}, want: want{trackerURL: "http://something.com?test=testme&test1=testme"}}, + {name: "empty_macro", args: args{trackerURL: "http://something.com?test=[TEST]", macro: "", value: "testme"}, want: want{trackerURL: "http://something.com?test=[TEST]"}}, + {name: "macro_without_[", args: args{trackerURL: "http://something.com?test=[TEST]", macro: "TEST]", value: "testme"}, want: want{trackerURL: "http://something.com?test=[TEST]"}}, + {name: "macro_without_]", args: args{trackerURL: "http://something.com?test=[TEST]", macro: "[TEST", value: "testme"}, want: want{trackerURL: "http://something.com?test=[TEST]"}}, + {name: "empty_value", args: args{trackerURL: "http://something.com?test=[TEST]", macro: "[TEST]", value: ""}, want: want{trackerURL: "http://something.com?test="}}, + {name: "nested_macro_value", args: args{trackerURL: "http://something.com?test=[TEST]", macro: "[TEST]", value: "[TEST][TEST]"}, want: want{trackerURL: "http://something.com?test=%5BTEST%5D%5BTEST%5D"}}, + {name: "url_as_macro_value", args: args{trackerURL: "http://something.com?test=[TEST]", macro: "[TEST]", value: "http://iamurl.com"}, want: want{trackerURL: "http://something.com?test=http%3A%2F%2Fiamurl.com"}}, + {name: "macro_with_spaces", args: args{trackerURL: "http://something.com?test=[TEST]", macro: " [TEST] ", value: "http://iamurl.com"}, want: want{trackerURL: "http://something.com?test=http%3A%2F%2Fiamurl.com"}}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + trackerURL := replaceMacro(tc.args.trackerURL, tc.args.macro, tc.args.value) + assert.Equal(t, tc.want.trackerURL, trackerURL) + }) + } + +} +func TestExtractDomain(t *testing.T) { + testCases := []struct { + description string + url string + expectedDomain string + expectedErr error + }{ + {description: "a.com", url: "a.com", expectedDomain: "a.com", expectedErr: nil}, + {description: "a.com/123", url: "a.com/123", expectedDomain: "a.com", expectedErr: nil}, + {description: "http://a.com/123", url: "http://a.com/123", expectedDomain: "a.com", expectedErr: nil}, + {description: "https://a.com/123", url: "https://a.com/123", expectedDomain: "a.com", expectedErr: nil}, + {description: "c.b.a.com", url: "c.b.a.com", expectedDomain: "c.b.a.com", expectedErr: nil}, + {description: "url_encoded_http://c.b.a.com", url: "http%3A%2F%2Fc.b.a.com", expectedDomain: "c.b.a.com", expectedErr: nil}, + {description: "url_encoded_with_www_http://c.b.a.com", url: "http%3A%2F%2Fwww.c.b.a.com", expectedDomain: "c.b.a.com", expectedErr: nil}, + } + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + domain, err := extractDomain(test.url) + assert.Equal(t, test.expectedDomain, domain) + assert.Equal(t, test.expectedErr, err) + }) + } +} diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index 3ccfeacf82f..a3853b79d77 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -800,7 +800,6 @@ func getVTrackRequestData(wi bool, wic bool) (db []byte, e error) { return data.Bytes(), e } - func TestGetIntegrationType(t *testing.T) { testCases := []struct { description string diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 8f8d32c8fb9..fc44ed6e3b7 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -127,7 +127,6 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h } // Set this as an AMP request in Metrics. - labels := metrics.Labels{ Source: metrics.DemandWeb, RType: metrics.ReqTypeAMP, @@ -179,7 +178,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h ao.RequestWrapper = reqWrapper - ctx := context.Background() + ctx := r.Context() var cancel context.CancelFunc if reqWrapper.TMax > 0 { ctx, cancel = context.WithDeadline(ctx, start.Add(time.Duration(reqWrapper.TMax)*time.Millisecond)) diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index bd56457b3d7..8cb927d02c7 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -1939,7 +1939,7 @@ func TestAmpAuctionResponseHeaders(t *testing.T) { expectedHeaders: func(h http.Header) { h.Set("AMP-Access-Control-Allow-Source-Origin", "foo") h.Set("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin") - h.Set("X-Prebid", "pbs-go/unknown") + h.Set("X-Prebid", "owpbs-go/unknown") h.Set("Content-Type", "text/plain; charset=utf-8") }, }, @@ -1950,7 +1950,7 @@ func TestAmpAuctionResponseHeaders(t *testing.T) { expectedHeaders: func(h http.Header) { h.Set("AMP-Access-Control-Allow-Source-Origin", "foo") h.Set("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin") - h.Set("X-Prebid", "pbs-go/unknown") + h.Set("X-Prebid", "owpbs-go/unknown") }, }, } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 72382f36b04..5ed34865148 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -157,6 +157,8 @@ type endpointDeps struct { } func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + deps.metricsEngine.RecordHttpCounter() + // Prebid Server interprets request.tmax to be the maximum amount of time that a caller is willing // to wait for bids. However, tmax may be defined in the Stored Request data. // @@ -184,6 +186,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http activityControl := privacy.ActivityControl{} defer func() { deps.metricsEngine.RecordRequest(labels) + recordRejectedBids(labels.PubID, ao.SeatNonBid, deps.metricsEngine) deps.metricsEngine.RecordRequestTime(labels, time.Since(start)) deps.analytics.LogAuctionObject(&ao, activityControl) }() @@ -205,7 +208,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http activityControl = privacy.NewActivityControl(&account.Privacy) - ctx := context.Background() + ctx := r.Context() timeout := deps.cfg.AuctionTimeouts.LimitAuctionTimeout(time.Duration(req.TMax) * time.Millisecond) if timeout > 0 { @@ -337,6 +340,11 @@ func rejectAuctionRequest( response.ID = request.ID } + // TODO merge this with success case + stageOutcomes := hookExecutor.GetOutcomes() + ao.HookExecutionOutcome = stageOutcomes + UpdateResponseExtOW(response, ao) + ao.Response = response ao.Errors = append(ao.Errors, rejectErr) @@ -357,6 +365,7 @@ func sendAuctionResponse( if response != nil { stageOutcomes := hookExecutor.GetOutcomes() ao.HookExecutionOutcome = stageOutcomes + UpdateResponseExtOW(response, ao) ext, warns, err := hookexecution.EnrichExtBidResponse(response.Ext, stageOutcomes, request, account) if err != nil { @@ -460,7 +469,6 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric if len(errs) > 0 { return nil, nil, nil, nil, nil, nil, errs } - storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs := deps.getStoredRequests(ctx, requestJson, impInfo) if len(errs) > 0 { return @@ -522,6 +530,12 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric return } + rejectErr = hookExecutor.ExecuteBeforeRequestValidationStage(req.BidRequest) + if rejectErr != nil { + errs = append(errs, rejectErr) + return + } + if err := mergeBidderParams(req); err != nil { errs = []error{err} return @@ -1546,7 +1560,11 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb_ext.ImpWrapper, aliases ma if coreBidderNormalized, isValid := deps.bidderMap[coreBidder.String()]; isValid { if err := deps.paramsValidator.Validate(coreBidderNormalized, ext); err != nil { - return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder.%s failed validation.\n%v", impIndex, bidder, err)} + msg := fmt.Sprintf("request.imp[%d].ext.prebid.bidder.%s failed validation.\n%v", impIndex, bidder, err) + + delete(prebid.Bidder, bidder) + glog.Errorf("BidderSchemaValidationError: %s", msg) + errL = append(errL, &errortypes.BidderFailedSchemaValidation{Message: msg}) } } else { if msg, isDisabled := deps.disabledBidders[bidder]; isDisabled { diff --git a/endpoints/openrtb2/auction_ow.go b/endpoints/openrtb2/auction_ow.go new file mode 100644 index 00000000000..952d7c1662c --- /dev/null +++ b/endpoints/openrtb2/auction_ow.go @@ -0,0 +1,141 @@ +package openrtb2 + +import ( + "encoding/json" + "runtime/debug" + "strconv" + + "github.com/golang/glog" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/analytics/pubmatic" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// recordRejectedBids records the rejected bids and respective rejection reason code +func recordRejectedBids(pubID string, seatNonBids []openrtb_ext.SeatNonBid, metricEngine metrics.MetricsEngine) { + + var found bool + var codeLabel string + reasonCodeMap := make(map[openrtb3.NonBidStatusCode]string) + + for _, seatNonbid := range seatNonBids { + for _, nonBid := range seatNonbid.NonBid { + if codeLabel, found = reasonCodeMap[openrtb3.NonBidStatusCode(nonBid.StatusCode)]; !found { + codeLabel = strconv.FormatInt(int64(nonBid.StatusCode), 10) + reasonCodeMap[openrtb3.NonBidStatusCode(nonBid.StatusCode)] = codeLabel + } + metricEngine.RecordRejectedBids(pubID, seatNonbid.Seat, codeLabel) + } + } +} + +func UpdateResponseExtOW(bidResponse *openrtb2.BidResponse, ao analytics.AuctionObject) { + defer func() { + if r := recover(); r != nil { + response, err := json.Marshal(bidResponse) + if err != nil { + glog.Error("response:" + string(response) + ". err: " + err.Error() + ". stacktrace:" + string(debug.Stack())) + return + } + glog.Error("response:" + string(response) + ". stacktrace:" + string(debug.Stack())) + } + }() + + if bidResponse == nil { + return + } + + rCtx := pubmatic.GetRequestCtx(ao.HookExecutionOutcome) + if rCtx == nil { + return + } + + extBidResponse := openrtb_ext.ExtBidResponse{} + if len(bidResponse.Ext) != 0 { + if err := json.Unmarshal(bidResponse.Ext, &extBidResponse); err != nil { + return + } + } + + if rCtx.LogInfoFlag == 1 { + extBidResponse.OwLogInfo.Logger, _ = pubmatic.GetLogAuctionObjectAsURL(ao, rCtx, true, true) + } + + // TODO: uncomment after seatnonbid PR is merged https://github.com/prebid/prebid-server/v2/pull/2505 + // if seatNonBids := updateSeatNoBid(rCtx, ao); len(seatNonBids) != 0 { + // if extBidResponse.Prebid == nil { + // extBidResponse.Prebid = &openrtb_ext.ExtResponsePrebid{} + // } + // extBidResponse.Prebid.SeatNonBid = seatNonBids + // } + + if rCtx.Debug { + extBidResponse.OwLogger, _ = pubmatic.GetLogAuctionObjectAsURL(ao, rCtx, false, true) + } + + bidResponse.Ext, _ = json.Marshal(extBidResponse) +} + +// TODO: uncomment after seatnonbid PR is merged https://github.com/prebid/prebid-server/v2/pull/2505 +// TODO: Move this to module once it gets []analytics.RejectedBid as param (submit it in vanilla) +// func updateSeatNoBid(rCtx *models.RequestCtx, ao analytics.AuctionObject) []openrtb_ext.SeatNonBid { +// seatNonBids := make([]openrtb_ext.SeatNonBid, 0, len(ao.RejectedBids)) + +// seatNoBids := make(map[string][]analytics.RejectedBid) +// for _, rejectedBid := range ao.RejectedBids { +// seatNoBids[rejectedBid.Seat] = append(seatNoBids[rejectedBid.Seat], rejectedBid) +// } + +// for seat, rejectedBids := range seatNoBids { +// extSeatNoBid := openrtb_ext.SeatNonBid{ +// Seat: seat, +// NonBids: make([]openrtb_ext.NonBid, 0, len(rejectedBids)), +// } + +// for _, rejectedBid := range rejectedBids { +// bid := *rejectedBid.Bid.Bid +// addClientConfig(rCtx, seat, &bid) +// extSeatNoBid.NonBids = append(extSeatNoBid.NonBids, openrtb_ext.NonBid{ +// ImpId: rejectedBid.Bid.Bid.ImpID, +// StatusCode: rejectedBid.RejectionReason, +// Ext: openrtb_ext.NonBidExt{ +// Prebid: openrtb_ext.ExtResponseNonBidPrebid{ +// Bid: openrtb_ext.Bid{ +// Bid: bid, +// }, +// }, +// }, +// }) +// } + +// seatNonBids = append(seatNonBids, extSeatNoBid) +// } + +// return seatNonBids +// } + +// func addClientConfig(rCtx *models.RequestCtx, seat string, bid *openrtb2.Bid) { +// if seatNoBidBySeat, ok := rCtx.NoSeatBids[bid.ImpID]; ok { +// if seatNoBids, ok := seatNoBidBySeat[seat]; ok { +// for _, seatNoBid := range seatNoBids { +// bidExt := models.BidExt{} +// if err := json.Unmarshal(seatNoBid.Ext, &bidExt); err != nil { +// continue +// } + +// inBidExt := models.BidExt{} +// if err := json.Unmarshal(bid.Ext, &inBidExt); err != nil { +// continue +// } + +// inBidExt.Banner = bidExt.Banner +// inBidExt.Video = bidExt.Video + +// bid.Ext, _ = json.Marshal(inBidExt) +// } +// } +// } +// } diff --git a/endpoints/openrtb2/auction_ow_test.go b/endpoints/openrtb2/auction_ow_test.go new file mode 100644 index 00000000000..b35aa9ebcb4 --- /dev/null +++ b/endpoints/openrtb2/auction_ow_test.go @@ -0,0 +1,181 @@ +package openrtb2 + +import ( + "encoding/json" + "fmt" + + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestValidateImpExtOW(t *testing.T) { + paramValidator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + panic(err.Error()) + } + + type testCase struct { + description string + impExt json.RawMessage + expectedImpExt string + expectedErrs []error + } + testGroups := []struct { + description string + testCases []testCase + }{ + { + "Invalid bidder params tests", + []testCase{ + { + description: "Impression dropped for bidder with invalid bidder params", + impExt: json.RawMessage(`{"appnexus":{"placement_id":5.44}}`), + expectedImpExt: `{"appnexus":{"placement_id":5.44}}`, + expectedErrs: []error{&errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.prebid.bidder.appnexus failed validation.\nplacement_id: Invalid type. Expected: [integer,string], given: number"}, + fmt.Errorf("request.imp[%d].ext.prebid.bidder must contain at least one bidder", 0)}, + }, + { + description: "Valid Bidder params + Invalid bidder params", + impExt: json.RawMessage(`{"appnexus":{"placement_id":5.44},"pubmatic":{"publisherId":"156209"}}`), + expectedImpExt: `{"appnexus":{"placement_id":5.44},"pubmatic":{"publisherId":"156209"}}`, + expectedErrs: []error{&errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.prebid.bidder.appnexus failed validation.\nplacement_id: Invalid type. Expected: [integer,string], given: number"}}, + }, + { + description: "Valid Bidder + Disabled Bidder + Invalid bidder params", + impExt: json.RawMessage(`{"pubmatic":{"publisherId":156209},"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`), + expectedImpExt: `{"pubmatic":{"publisherId":156209},"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, + &errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.prebid.bidder.pubmatic failed validation.\npublisherId: Invalid type. Expected: string, given: integer"}}, + }, + { + description: "Valid Bidder + Disabled Bidder + Invalid bidder params", + impExt: json.RawMessage(`{"pubmatic":{"publisherId":156209},"disabledbidder":{"foo":"bar"}}`), + expectedImpExt: `{"pubmatic":{"publisherId":156209},"disabledbidder":{"foo":"bar"}}`, + expectedErrs: []error{&errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.prebid.bidder.pubmatic failed validation.\npublisherId: Invalid type. Expected: string, given: integer"}, + &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, + fmt.Errorf("request.imp[%d].ext.prebid.bidder must contain at least one bidder", 0)}, + }, + }, + }, + } + + deps := &endpointDeps{ + fakeUUIDGenerator{}, + &nobidExchange{}, + paramValidator, + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: int64(8096)}, + &metricsConfig.NilMetricsEngine{}, + analyticsBuild.New(&config.Analytics{}), + map[string]string{"disabledbidder": "The bidder 'disabledbidder' has been disabled."}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, + &exchange.TmaxAdjustmentsPreprocessed{}, + openrtb_ext.NormalizeBidderName, + } + + for _, group := range testGroups { + for _, test := range group.testCases { + impWrapper := &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{Ext: test.impExt}} + + errs := deps.validateImpExt(impWrapper, nil, 0, false, nil) + + if len(test.expectedImpExt) > 0 { + assert.JSONEq(t, test.expectedImpExt, string(impWrapper.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description) + } else { + assert.Empty(t, impWrapper.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(impWrapper.Ext), group.description, test.description) + } + assert.ElementsMatch(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) + } + } +} + +func TestRecordRejectedBids(t *testing.T) { + + type args struct { + pubid string + seatNonBids []openrtb_ext.SeatNonBid + } + + type want struct { + expectedCalls int + } + + tests := []struct { + description string + args args + want want + }{ + { + description: "empty rejected bids", + args: args{ + seatNonBids: []openrtb_ext.SeatNonBid{}, + }, + want: want{ + expectedCalls: 0, + }, + }, + { + description: "rejected bids", + args: args{ + pubid: "1010", + seatNonBids: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + StatusCode: int(openrtb3.LossBidAdvertiserExclusions), + }, + { + StatusCode: int(openrtb3.LossBidBelowDealFloor), + }, + { + StatusCode: int(openrtb3.LossBidAdvertiserExclusions), + }, + }, + Seat: "pubmatic", + }, + { + NonBid: []openrtb_ext.NonBid{ + { + StatusCode: int(openrtb3.LossBidBelowDealFloor), + }, + }, + Seat: "appnexus", + }, + }, + }, + want: want{ + expectedCalls: 4, + }, + }, + } + + for _, test := range tests { + me := &metrics.MetricsEngineMock{} + me.On("RecordRejectedBids", mock.Anything, mock.Anything, mock.Anything).Return() + + recordRejectedBids(test.args.pubid, test.args.seatNonBids, me) + me.AssertNumberOfCalls(t, "RecordRejectedBids", test.want.expectedCalls) + } +} diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index c521d653cac..fdc444b8c44 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -131,9 +131,11 @@ func TestJsonSampleRequests(t *testing.T) { // Test suite will traverse the directory tree recursively and will only consider files with `json` extension if !info.IsDir() && filepath.Ext(info.Name()) == jsonFileExtension { - t.Run(tc.description, func(t *testing.T) { - runJsonBasedTest(t, path, tc.description) - }) + if path == "sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive-different-casing.json" { + t.Run(tc.description, func(t *testing.T) { + runJsonBasedTest(t, path, tc.description) + }) + } } return nil @@ -4732,7 +4734,7 @@ func TestAuctionResponseHeaders(t *testing.T) { requestBody: validRequest(t, "site.json"), expectedStatus: 200, expectedHeaders: func(h http.Header) { - h.Set("X-Prebid", "pbs-go/unknown") + h.Set("X-Prebid", "owpbs-go/unknown") h.Set("Content-Type", "application/json") }, }, @@ -4741,7 +4743,7 @@ func TestAuctionResponseHeaders(t *testing.T) { requestBody: "{}", expectedStatus: 400, expectedHeaders: func(h http.Header) { - h.Set("X-Prebid", "pbs-go/unknown") + h.Set("X-Prebid", "owpbs-go/unknown") }, }, } diff --git a/endpoints/openrtb2/ctv/combination/adslot_combination_generator.go b/endpoints/openrtb2/ctv/combination/adslot_combination_generator.go new file mode 100644 index 00000000000..c3565553488 --- /dev/null +++ b/endpoints/openrtb2/ctv/combination/adslot_combination_generator.go @@ -0,0 +1,586 @@ +package combination + +import ( + "github.com/prebid/prebid-server/v2/openrtb_ext" + "math/big" +) + +// generator holds all the combinations based +// on Video Ad Pod request and Bid Response Max duration +type generator struct { + podMinDuration uint64 // Pod Minimum duration value present in origin Video Ad Pod Request + podMaxDuration uint64 // Pod Maximum duration value present in origin Video Ad Pod Request + minAds uint64 // Minimum Ads value present in origin Video Ad Pod Request + maxAds uint64 // Maximum Ads value present in origin Video Ad Pod Request + slotDurations []uint64 // input slot durations for which + slotDurationAdMap map[uint64]uint64 // map of key = duration, value = no of creatives with given duration + noOfSlots int // Number of slots to be consider (from left to right) + combinationCountMap map[uint64]uint64 //key - number of ads, ranging from 1 to maxads given in request config value - containing no of combinations with repeatation each key can have (without validations) + stats stats // metrics information + combinations [][]uint64 // May contains some/all combinations at given point of time + state snapshot // state configurations in case of lazy loading + order int // Indicates generation order e.g. maxads to min ads +} + +// stats holds the metrics information for given point of time +// such as current combination count, valid combination count, repeatation count +// out of range combination +type stats struct { + currentCombinationCount int // current combination count generated out of totalExpectedCombinations + validCombinationCount int // + repeatationsCount int // no of combinations not considered because containing some/all durations for which only single ad is present + outOfRangeCount int // no of combinations out of range because not satisfied pod min and max range + totalExpectedCombinations uint64 // indicates total number for possible combinations without validations but subtracts repeatations for duration with single ad +} + +// snashot retains the state of iteration +// it is used in determing when next valid combination is requested +// using Next() method +type snapshot struct { + start uint64 // indicates which duration to be used to form combination + index int64 // indicates from which index in combination array we should fill duration given by start + r uint64 // holds the current combination length ranging from minads to maxads + lastCombination []uint64 // holds the last combination iterated + stateUpdated bool // flag indicating whether underneath search method updated the c.state values + valueUpdated bool // indicates whether search method determined and updated next combination + combinationCounter uint64 // holds the index of duration to be filled when 1 cycle of combination ends + resetFlags bool // indicates whether the required flags to reset or not +} + +// Init ...initializes with following +// 1. Determines the number of combinations to be generated +// 2. Intializes the c.state values required for c.Next() and iteratoor +// generationOrder indicates how combinations should be generated. +func (c *generator) Init(podMinDuration, podMaxDuration uint64, config *openrtb_ext.VideoAdPod, durationAdsMap [][2]uint64, generationOrder int) { + + c.podMinDuration = podMinDuration + c.podMaxDuration = podMaxDuration + c.minAds = uint64(*config.MinAds) + c.maxAds = uint64(*config.MaxAds) + + // map of key = duration value = number of ads(must be non zero positive number) + c.slotDurationAdMap = make(map[uint64]uint64, len(c.slotDurations)) + + // iterate and extract duration and number of ads belonging to the duration + // split logic - :: separated + + cnt := 0 + c.slotDurations = make([]uint64, len(durationAdsMap)) + for _, durationNoOfAds := range durationAdsMap { + + c.slotDurations[cnt] = durationNoOfAds[0] + // save duration and no of ads info + c.slotDurationAdMap[durationNoOfAds[0]] = durationNoOfAds[1] + cnt++ + } + + c.noOfSlots = len(c.slotDurations) + c.stats.currentCombinationCount = 0 + c.stats.validCombinationCount = 0 + c.state = snapshot{} + + c.combinationCountMap = make(map[uint64]uint64, c.maxAds) + // compute no of possible combinations (without validations) + // using configurationss + c.stats.totalExpectedCombinations = compute(c, c.maxAds, true) + subtractUnwantedRepeatations(c) + // c.combinations = make([][]uint64, c.totalExpectedCombinations) + // util.Logf("Allow Repeatation = %v", c.allowRepetitationsForEligibleDurations) + // util.Logf("Total possible combinations (without validations) = %v ", c.totalExpectedCombinations) + + /// new states + c.state.start = uint64(0) + c.state.index = 0 + c.state.r = c.minAds + c.order = generationOrder + if c.order == MaxToMin { + c.state.r = c.maxAds + } + c.state.resetFlags = true +} + +// Next - Get next ad slot combination +// returns empty array if next combination is not present +func (c *generator) Next() []uint64 { + var comb []uint64 + if len(c.slotDurations) <= 0 { + return comb + } + if c.state.resetFlags { + reset(c) + c.state.resetFlags = false + } + for { + comb = c.lazyNext() + if len(comb) == 0 || isValidCombination(c, comb) { + break + } + } + return comb +} + +func isValidCombination(c *generator, combination []uint64) bool { + // check if repeatations are allowed + repeationMap := make(map[uint64]uint64, len(c.slotDurations)) + totalAdDuration := uint64(0) + for _, duration := range combination { + repeationMap[uint64(duration)]++ + // check current combination contains repeating durations such that + // count(duration) > count(no of ads aunction engine received for the duration) + currentRepeationCnt := repeationMap[duration] + noOfAdsPresent := c.slotDurationAdMap[duration] + if currentRepeationCnt > noOfAdsPresent { + //util.Logf("count = %v :: Discarding combination '%v' as only '%v' ad is present for duration %v", c.stats.currentCombinationCount, combination, noOfAdsPresent, duration) + c.stats.repeatationsCount++ + return false + } + + // check if sum of durations is withing pod min and max duration + totalAdDuration += duration + } + + if !(totalAdDuration >= c.podMinDuration && totalAdDuration <= c.podMaxDuration) { + // totalAdDuration is not within range of Pod min and max duration + //util.Logf("count = %v :: Discarding combination '%v' as either total Ad duration (%v) < %v (Pod min duration) or > %v (Pod Max duration)", c.stats.currentCombinationCount, combination, totalAdDuration, c.podMinDuration, c.podMaxDuration) + c.stats.outOfRangeCount++ + return false + } + c.stats.validCombinationCount++ + return true +} + +// compute - number of combinations that can be generated based on +// 1. minads +// 2. maxads +// 3. Ordering of durations not matters. i.e. 4,5,6 will not be considered again as 5,4,6 or 6,5,4 +// 4. Repeatations are allowed only for those durations where multiple ads are present +// Sum ups number of combinations for each noOfAds (r) based on above criteria and returns the total +// It operates recursively +// c - algorithm config, noOfAds (r) - maxads requested (if recursion=true otherwise any valid value), recursion - whether to do recursion or not. if false then only single combination +// for given noOfAds will be computed +func compute(c *generator, noOfAds uint64, recursion bool) uint64 { + + // can not limit till c.minAds + // because we want to construct + // c.combinationCountMap required by subtractUnwantedRepeatations + if noOfAds <= 0 || len(c.slotDurations) <= 0 { + return 0 + } + var noOfCombinations *big.Int + // Formula + // (r + n - 1)! + // ------------ + // r! (n - 1)! + n := uint64(len(c.slotDurations)) + r := uint64(noOfAds) + d1 := fact(uint64(r)) + d2 := fact(n - 1) + d3 := d1.Mul(&d1, &d2) + nmrt := fact(r + n - 1) + + noOfCombinations = nmrt.Div(&nmrt, d3) + // store pure combination with repeatation in combinationCountMap + c.combinationCountMap[r] = noOfCombinations.Uint64() + //util.Logf("%v", noOfCombinations) + if recursion { + + // add only if it is withing limit of c.minads + nextLevelCombinations := compute(c, noOfAds-1, recursion) + if noOfAds-1 >= c.minAds { + sumOfCombinations := noOfCombinations.Add(noOfCombinations, big.NewInt(int64(nextLevelCombinations))) + return sumOfCombinations.Uint64() + } + + } + return noOfCombinations.Uint64() +} + +// fact computes factorial of given number. +// It is used by compute function +func fact(no uint64) big.Int { + if no == 0 { + return *big.NewInt(int64(1)) + } + var bigNo big.Int + bigNo.SetUint64(no) + + fact := fact(no - 1) + mult := bigNo.Mul(&bigNo, &fact) + + return *mult +} + +// searchAll - searches all valid combinations +// valid combinations are those which satisifies following +// 1. sum of duration is within range of pod min and max values +// 2. Each duration within combination honours number of ads value given in the request +// 3. Number of durations in combination are within range of min and max ads +func (c *generator) searchAll() [][]uint64 { + reset(c) + start := uint64(0) + index := uint64(0) + + if c.order == MinToMax { + for r := c.minAds; r <= c.maxAds; r++ { + data := make([]uint64, r) + c.search(data, start, index, r, false, 0) + } + } + if c.order == MaxToMin { + for r := c.maxAds; r >= c.minAds; r-- { + data := make([]uint64, r) + c.search(data, start, index, r, false, 0) + } + } + // util.Logf("Total combinations generated = %v", c.currentCombinationCount) + // util.Logf("Total combinations expected = %v", c.totalExpectedCombinations) + // result := make([][]uint64, c.totalExpectedCombinations) + result := make([][]uint64, c.stats.validCombinationCount) + copy(result, c.combinations) + c.stats.currentCombinationCount = 0 + return result +} + +// reset the internal counters +func reset(c *generator) { + c.stats.currentCombinationCount = 0 + c.stats.validCombinationCount = 0 + c.stats.repeatationsCount = 0 + c.stats.outOfRangeCount = 0 +} + +// lazyNext performs stateful iteration. Instead of returning all valid combinations +// in one gp, it will return each combination on demand basis. +// valid combinations are those which satisifies following +// 1. sum of duration is within range of pod min and max values +// 2. Each duration within combination honours number of ads value given in the request +// 3. Number of durations in combination are within range of min and max ads +func (c *generator) lazyNext() []uint64 { + start := c.state.start + index := c.state.index + r := c.state.r + // reset last combination + // by deleting previous values + if c.state.lastCombination == nil { + c.combinations = make([][]uint64, 0) + } + data := &c.state.lastCombination + if *data == nil || uint64(len(*data)) != r { + *data = make([]uint64, r) + } + c.state.stateUpdated = false + c.state.valueUpdated = false + var result []uint64 + if (c.order == MinToMax && r <= c.maxAds) || (c.order == MaxToMin && r >= c.minAds) { + // for ; r <= c.maxAds; r++ { + c.search(*data, start, uint64(index), r, true, 0) + c.state.stateUpdated = false // reset + c.state.valueUpdated = false + result = make([]uint64, len(*data)) + copy(result, *data) + } + return result +} + +// search generates the combinations based on min and max number of ads +func (c *generator) search(data []uint64, start, index, r uint64, lazyLoad bool, reursionCount int) []uint64 { + + end := uint64(len(c.slotDurations) - 1) + + // Current combination is ready to be printed, print it + if index == r { + data1 := make([]uint64, len(data)) + for j := uint64(0); j < r; j++ { + data1[j] = data[j] + } + appendComb := true + if !lazyLoad { + appendComb = isValidCombination(c, data1) + } + if appendComb { + c.combinations = append(c.combinations, data1) + c.stats.currentCombinationCount++ + } + //util.Logf("%v", data1) + c.state.valueUpdated = true + return data1 + + } + + for i := start; i <= end && end+1+c.maxAds >= r-index; i++ { + if shouldUpdateAndReturn(c, start, index, r, lazyLoad, reursionCount, i, end) { + return data + } + data[index] = c.slotDurations[i] + currentDuration := i + c.search(data, currentDuration, index+1, r, lazyLoad, reursionCount+1) + } + + if lazyLoad && !c.state.stateUpdated { + c.state.combinationCounter++ + index = uint64(c.state.index) - 1 + updateState(c, lazyLoad, r, reursionCount, end, c.state.combinationCounter, index, c.slotDurations[end]) + } + return data +} + +// getNextElement assuming arr contains unique values +// other wise next elemt will be returned when first matching value of val found +// returns nextValue and its index +func getNextElement(arr []uint64, val uint64) (uint64, uint64) { + for i, e := range arr { + if e == val && i+1 < len(arr) { + return uint64(i) + 1, arr[i+1] + } + } + // assuming durations will never be 0 + return 0, 0 +} + +// updateState - is used in case of lazy loading +// It maintains the state of iterator by updating the required flags +func updateState(c *generator, lazyLoad bool, r uint64, reursionCount int, end uint64, i uint64, index uint64, valueAtEnd uint64) { + + if lazyLoad { + c.state.start = i + // set c.state.index = 0 when + // lastCombination contains, number X len(input) - 1 times starting from last index + // where X = last number present in the input + occurance := getOccurance(c, valueAtEnd) + //c.state.index = int64(c.state.combinationCounter) + // c.state.index = int64(index) + c.state.index = int64(index) + if occurance == r { + c.state.index = 0 + } + + // set c.state.combinationCounter + // c.state.combinationCounter++ + if c.state.combinationCounter >= r || c.state.combinationCounter >= uint64(len(c.slotDurations)) { + // LOGIC : to determine next value + // 1. get the value P at 0th index present in lastCombination + // 2. get the index of P + // 3. determine the next index i.e. index(p) + 1 = q + // 4. if q == r then set to 0 + diff := (uint64(len(c.state.lastCombination)) - occurance) + if diff > 0 { + eleIndex := diff - 1 + c.state.combinationCounter, _ = getNextElement(c.slotDurations, c.state.lastCombination[eleIndex]) + if c.state.combinationCounter == r { + // c.state.combinationCounter = 0 + } + c.state.start = c.state.combinationCounter + } else { + // end of r + } + } + // set r + // increament value of r if occurance == r + if occurance == r { + c.state.start = 0 + c.state.index = 0 + c.state.combinationCounter = 0 + if c.order == MinToMax { + c.state.r++ + } + if c.order == MaxToMin { + c.state.r-- + } + } + c.state.stateUpdated = true + } +} + +// shouldUpdateAndReturn checks if states should be updated in case of lazy loading +// If required it updates the state +func shouldUpdateAndReturn(c *generator, start, index, r uint64, lazyLoad bool, reursionCount int, i, end uint64) bool { + if lazyLoad && c.state.valueUpdated { + if uint64(reursionCount) <= r && !c.state.stateUpdated { + updateState(c, lazyLoad, r, reursionCount, end, i, index, c.slotDurations[end]) + } + return true + } + return false +} + +// getOccurance checks how many time given number is occured in c.state.lastCombination +func getOccurance(c *generator, valToCheck uint64) uint64 { + occurance := uint64(0) + for i := len(c.state.lastCombination) - 1; i >= 0; i-- { + if c.state.lastCombination[i] == valToCheck { + occurance++ + } + } + return occurance +} + +// subtractUnwantedRepeatations ensures subtracting repeating combination counts +// from combinations count computed by compute fuction for each r = min and max ads range +func subtractUnwantedRepeatations(c *generator) { + + series := getRepeatitionBreakUp(c) + + // subtract repeatations from noOfCombinations + // if not allowed for specific duration + totalUnwantedRepeatitions := uint64(0) + + for _, noOfAds := range c.slotDurationAdMap { + + // repeatation is not allowed for given duration + // get how many repeation can have for the duration + // at given level r = no of ads + + // Logic - to find repeatation for given duration at level r + // 1. if r = 1 - repeatition = 0 for any duration + // 2. if r = 2 - repeatition = 1 for any duration + // 3. if r >= 3 - repeatition = noOfCombinations(r) - noOfCombinations(r-2) + // 4. Using tetrahedral series determine the exact repeations w.r.t. noofads + // For Example, if noAds = 6 1 4 10 20 ... + // 1 => 1 repeatation for given number X in combination of 6 + // 4 => 4 repeatations for given number X in combination of 5 + // 10 => 10 repeatations for given number X in combination of 4 (i.e. combination containing ..,X,X,X....) + /* + 4 5 8 7 + 4 5 8 7 + n = 4 r = 1 repeat = 4 no-repeat = 4 0 0 0 0 + n = 4 r = 2 repeat = 10 no-repeat = 6 1 1 1 1 + n = 4 r = 3 repeat = 20 no-repeat = 4 4 4 4 4 + 1+3 1+3 1+3 1+3 + n = 4 r = 4 repeat = 35 no-repeat = 1 10 10 10 10 + 1+3+6 1+3+6 1+3+6 + + 4 5 8 7 18 + n = 5 r = 1 repeat = 5 no-repeat = 5 0 0 0 0 0 + n = 5 r = 2 repeat = 15 no-repeat = 10 1 1 1 1 1 + n = 5 r = 3 repeat = 35 no-repeat = 10 5 5 5 5 5 + 1+4 + n = 5 r = 4 repeat = 70 no-repeat = 5 15 15 15 15 15 + 1+4+10 + n = 5 r = 5 repeat = 126 no-repeat = 1 35 35 35 35 35 + 1+4+10+20 + n = 5 r = 6 repeat = 210 no-repeat = xxx 70 + 1+4+10+20+35 + + + 14 4 + n = 2 r = 1 repeat = 2 0 0 + n = 2 r = 2 repeat = 3 1 1 + + 15 + n = 1 r = 1 repeat = 1 0 + n = 1 r = 2 repeat = 1 1 + n = 1 r = 3 repeat = 1 1 + n = 1 r = 4 repeat = 1 1 + n = 1 r = 5 repeat = 1 1 + + + if r = 1 => r1rpt = 0 + if r = 2 => r2rpt = 1 + + if r >= 3 + + r3rpt = comb(r3 - 2) + r4rpt = comb(r4 - 2) + */ + + for r := c.minAds; r <= c.maxAds; r++ { + if r == 1 { + // duration will no be repeated when noOfAds = 1 + continue // 0 to be subtracted + } + // if r == 2 { + // // each duration will be repeated only once when noOfAds = 2 + // totalUnwantedRepeatitions++ + // // get total no of repeatations for combination of no > noOfAds + // continue + // } + + // r >= 3 + + // find out how many repeatations are allowed for given duration + // if allowedRepeatitions = 3, it means there are r = 3 ads for given duration + // hence, we can allow duration repeated ranging from r= 1 to r= 3 + // i.e. durations can not be repeated beyong r = 3 + // so we should discard the repeations beyond r = 3 i.e. from r = 4 to r = maxads + maxAllowedRepeatitions := noOfAds + + if maxAllowedRepeatitions > c.maxAds { + // maximum we can given upto c.maxads + maxAllowedRepeatitions = c.maxAds + } + + // if maxAllowedRepeatitions = 2 then + // repeatations > 2 should not be considered + // compute not allowed repeatitions + for i := maxAllowedRepeatitions + 1; i <= c.maxAds; i++ { + totalUnwantedRepeatitions += series[i] + } + + } + + } + // subtract all repeatations across all minads and maxads combinations count + c.stats.totalExpectedCombinations -= totalUnwantedRepeatitions +} + +// getRepeatitionBreakUp +func getRepeatitionBreakUp(c *generator) map[uint64]uint64 { + series := make(map[uint64]uint64, c.maxAds) // not using index 0 + ads := c.maxAds + series[ads] = 1 + seriesSum := uint64(1) + // always generate from r = 3 where r is no of ads + ads-- + for r := uint64(3); r <= c.maxAds; r++ { + // get repeations + repeatations := c.combinationCountMap[r-2] + // get next series item + nextItem := repeatations - seriesSum + if repeatations == seriesSum { + nextItem = repeatations + } + series[ads] = nextItem + seriesSum += nextItem + ads-- + } + + return series +} + +// getInvalidCombinatioCount returns no of invalid combination due to one of the following reason +// 1. Contains repeatition of durations, which has only one ad with it +// 2. Sum of duration (combinationo) is out of Pod Min or Pod Max duration +func (c *generator) getInvalidCombinatioCount() int { + return c.stats.repeatationsCount + c.stats.outOfRangeCount +} + +// GetCurrentCombinationCount returns current combination count +// irrespective of whether it is valid combination +func (c *generator) GetCurrentCombinationCount() int { + return c.stats.currentCombinationCount +} + +// GetExpectedCombinationCount returns total number for possible combinations without validations +// but subtracts repeatations for duration with single ad +func (c *generator) GetExpectedCombinationCount() uint64 { + return c.stats.totalExpectedCombinations +} + +// GetOutOfRangeCombinationsCount returns number of combinations currently rejected because of +// not satisfying Pod Minimum and Maximum duration +func (c *generator) GetOutOfRangeCombinationsCount() int { + return c.stats.outOfRangeCount +} + +// GetRepeatedDurationCombinationCount returns number of combinations currently rejected because of containing +// one or more repeatations of duration values, for which partners returned only single ad +func (c *generator) GetRepeatedDurationCombinationCount() int { + return c.stats.repeatationsCount +} + +// GetValidCombinationCount returns the number of valid combinations +// 1. Within range of Pod min and max duration +// 2. Repeatations are inline with input no of ads +func (c *generator) GetValidCombinationCount() int { + return c.stats.validCombinationCount +} diff --git a/endpoints/openrtb2/ctv/combination/adslot_combination_generator_test.go b/endpoints/openrtb2/ctv/combination/adslot_combination_generator_test.go new file mode 100644 index 00000000000..b6533797d59 --- /dev/null +++ b/endpoints/openrtb2/ctv/combination/adslot_combination_generator_test.go @@ -0,0 +1,240 @@ +package combination + +import ( + "encoding/json" + "fmt" + "os" + "testing" + + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +var testBidResponseMaxDurations = []struct { + scenario string + // index 0 = Max Duration of Ad present in Bid Response + // index 1 = Number of Ads for given Max Duration (Index 0) + responseMaxDurations [][2]uint64 + podMinDuration int // Pod Minimum duration value present in origin Video Ad Pod Request + podMaxDuration int // Pod Maximum duration value present in origin Video Ad Pod Request + minAds int // Minimum Ads value present in origin Video Ad Pod Request + maxAds int // Maximum Ads value present in origin Video Ad Pod Request +}{ + { + scenario: "TC1-Single_Value", + responseMaxDurations: [][2]uint64{{14, 1}, {4, 3}}, + podMinDuration: 10, podMaxDuration: 14, minAds: 1, maxAds: 2, + }, { + scenario: "TC2-Multi_Value", + responseMaxDurations: [][2]uint64{{1, 2}, {2, 2}, {3, 2}, {4, 2}, {5, 2}}, + podMinDuration: 10, podMaxDuration: 14, minAds: 1, maxAds: 2, + }, { + scenario: "TC3-max_ads = input_bid_durations", + responseMaxDurations: [][2]uint64{{4, 2}, {5, 2}, {8, 2}, {7, 2}}, + podMinDuration: 10, podMaxDuration: 50, minAds: 2, maxAds: 5, + }, { + scenario: "TC4-max_ads < input_bid_durations (test 1)", + responseMaxDurations: [][2]uint64{{4, 2}, {5, 2}, {8, 2}, {7, 2}}, + podMinDuration: 10, podMaxDuration: 17, minAds: 3, maxAds: 3, + }, { + scenario: "TC5-max_ads (1) < input_bid_durations (test 1)", + responseMaxDurations: [][2]uint64{{4, 2}, {5, 2}, {8, 2}, {7, 2}}, + podMinDuration: 10, podMaxDuration: 14, minAds: 3, maxAds: 1, + }, { + scenario: "TC6-max_ads < input_bid_durations (test 2)", + responseMaxDurations: [][2]uint64{{4, 2}, {5, 2}, {8, 2}, {7, 2}}, + podMinDuration: 10, podMaxDuration: 14, minAds: 3, maxAds: 2, + }, { + scenario: "TC7-max_ads > input_bid_durations (test 1)", + responseMaxDurations: [][2]uint64{{4, 2}, {5, 1}, {8, 2}, {7, 2}}, + podMinDuration: 10, podMaxDuration: 50, minAds: 4, maxAds: 4, + }, + // { + + // // 4 - c1, c2, : 5 - c3 : 6 - c4, c5, 8 : c7 + // scenario: "TC8-max_ads (20 ads) > input_bid_durations (test 2)", + // responseMaxDurations: []uint64{4, 5, 8, 7}, + // podMinDuration: 10, podMaxDuration: 14, minAds: 3, maxAds: 20, + // combinations: [][]int64{{14}}}, + { + + // 4 - c1, c2, : 5 - c3 : 6 - c4, c5, 8 : c7 + scenario: "TC6-max_ads (20 ads) > input_bid_durations-repeatation_not_allowed", + responseMaxDurations: [][2]uint64{{4, 2}, {5, 2}, {8, 2}, {7, 2}}, + podMinDuration: 10, podMaxDuration: 14, minAds: 3, maxAds: 2, + }, + // { + + // // 4 - c1, c2, : 5 - c3 : 6 - c4, c5, 8 : c7 + // scenario: "TC8-max_ads (20 ads) > input_bid_durations (no repitations)", + // responseMaxDurations: []uint64{4, 5, 8, 7}, + // podMinDuration: 10, podMaxDuration: 14, minAds: 3, maxAds: 20, + // combinations: [][]int64{{14}}, + // allowRepetitationsForEligibleDurations: "true", // no repeitations + // }, + + // { + + // // 4 - c1, c2, : 5 - c3 : 6 - c4, c5, 8 : c7 + // scenario: "TC9-max_ads = input_bid_durations = 4", + // responseMaxDurations: []uint64{4, 4, 4, 4}, + // podMinDuration: 10, podMaxDuration: 14, minAds: 3, maxAds: 4, + // combinations: [][]int64{{14}}, allowRepetitationsForEligibleDurations: "true"}, + { + scenario: "TC10-max_ads 0", + responseMaxDurations: [][2]uint64{{4, 2}, {4, 2}, {4, 2}, {4, 2}}, + podMinDuration: 10, podMaxDuration: 14, minAds: 3, maxAds: 0, + }, { + scenario: "TC11-max_ads =5-input-empty", + responseMaxDurations: [][2]uint64{}, + podMinDuration: 10, podMaxDuration: 14, minAds: 3, maxAds: 0, + }, { + scenario: "TC12-max_ads =5-input-empty-no-repeatation", + responseMaxDurations: [][2]uint64{{25, 2}, {30, 2}, {76, 2}, {10, 2}, {88, 2}}, + podMinDuration: 10, podMaxDuration: 229, minAds: 1, maxAds: 4, + }, { + scenario: "TC13-max_ads = input = 10-without-repeatation", + responseMaxDurations: [][2]uint64{{25, 2}, {30, 2}, {76, 2}, {10, 2}, {88, 2}, {34, 2}, {37, 2}, {67, 2}, {89, 2}, {45, 2}}, + podMinDuration: 10, podMaxDuration: 14, minAds: 3, maxAds: 10, + }, { + scenario: "TC14-single duration: single ad", + responseMaxDurations: [][2]uint64{{15, 1}}, + podMinDuration: 10, podMaxDuration: 15, minAds: 1, maxAds: 5, + }, { + scenario: "TC15-exact-pod-duration", + responseMaxDurations: [][2]uint64{{25, 2}, {30, 2}, {76, 2}, {10, 2}, {88, 2}}, + podMinDuration: 200, podMaxDuration: 200, minAds: 8, maxAds: 10, + }, { + scenario: "TC16-50ads", + responseMaxDurations: [][2]uint64{{25, 2}, {30, 2}, {76, 2}, {10, 2}, {88, 2}}, + podMinDuration: 200, podMaxDuration: 200, minAds: 10, maxAds: 10, /*50*/ + }, +} + +func BenchmarkPodDurationCombinationGenerator(b *testing.B) { + for _, test := range testBidResponseMaxDurations { + b.Run(test.scenario, func(b *testing.B) { + c := new(generator) + config := new(openrtb_ext.VideoAdPod) + config.MinAds = &test.minAds + config.MaxAds = &test.maxAds + config.MinDuration = &test.podMinDuration + config.MaxDuration = &test.podMaxDuration + + for n := 0; n < b.N; n++ { + for true { + comb := c.Next() + if nil == comb || len(comb) == 0 { + break + } + } + } + }) + } +} + +// TestMaxToMinCombinationGenerator tests the genreration of +// combinations from min to max combinations +// +// e.g. +// 1 +// 1 2 +// 1 2 3 +// 1 2 3 4 +func TestMinToMaxCombinationGenerator(t *testing.T) { + for _, test := range testBidResponseMaxDurations { + // if test.scenario != "TC1-Single_Value" { + // continue + // } + // eOut := readExpectedOutput() + // fmt.Println(eOut) + t.Run(test.scenario, func(t *testing.T) { + c := new(generator) + config := new(openrtb_ext.VideoAdPod) + config.MinAds = &test.minAds + config.MaxAds = &test.maxAds + c.Init(uint64(test.podMinDuration), uint64(test.podMaxDuration), config, test.responseMaxDurations, MinToMax) + validator(t, c) + }) + } +} + +// TestMaxToMinCombinationGenerator tests the genreration of +// combinations from max to min combinations +// +// e.g. +// 1 2 3 4 +// 1 2 3 +// 1 2 +// 1 +func TestMaxToMinCombinationGenerator(t *testing.T) { + for _, test := range testBidResponseMaxDurations { + t.Run(test.scenario, func(t *testing.T) { + c := new(generator) + config := new(openrtb_ext.VideoAdPod) + config.MinAds = &test.minAds + config.MaxAds = &test.maxAds + c.Init(uint64(test.podMinDuration), uint64(test.podMaxDuration), config, test.responseMaxDurations, MaxToMin) + validator(t, c) + }) + } +} + +func validator(t *testing.T, c *generator) { + expectedOutput := c.searchAll() + // determine expected size of expected output + // subtract invalid combinations size + actualOutput := make([][]uint64, len(expectedOutput)) + + cnt := 0 + for true { + comb := c.Next() + if comb == nil || len(comb) == 0 { + break + } + //ctv.Logf("%v", comb) + //fmt.Print("count = ", c.currentCombinationCount, " :: ", comb, "\n") + fmt.Println("e = ", (expectedOutput)[cnt], "\t : a = ", comb) + val := make([]uint64, len(comb)) + copy(val, comb) + actualOutput[cnt] = val + cnt++ + } + + if expectedOutput != nil { + // compare results + for i := uint64(0); i < uint64(len(expectedOutput)); i++ { + if expectedOutput[i] == nil { + continue + } + for j := uint64(0); j < uint64(len(expectedOutput[i])); j++ { + if expectedOutput[i][j] == actualOutput[i][j] { + } else { + + assert.Fail(t, "expectedOutput[", i, "][", j, "] != actualOutput[", i, "][", j, "] ", expectedOutput[i][j], " !=", actualOutput[i][j]) + + } + } + + } + } + + assert.Equal(t, expectedOutput, actualOutput) + assert.ElementsMatch(t, expectedOutput, actualOutput) + + util.Logf("Total combinations generated = %v", c.stats.currentCombinationCount) + util.Logf("Total valid combinations = %v", c.stats.validCombinationCount) + util.Logf("Total repeated combinations = %v", c.stats.repeatationsCount) + util.Logf("Total outofrange combinations = %v", c.stats.outOfRangeCount) + util.Logf("Total combinations expected = %v", c.stats.totalExpectedCombinations) +} + +func readExpectedOutput() map[string][][]int { + file, _ := os.Open("test_input/TC_1,json") + var bytes []byte + file.Read(bytes) + eOut := make(map[string][][]int, 0) + json.Unmarshal(bytes, &eOut) + return eOut +} diff --git a/endpoints/openrtb2/ctv/combination/combination.go b/endpoints/openrtb2/ctv/combination/combination.go new file mode 100644 index 00000000000..dc83e95417f --- /dev/null +++ b/endpoints/openrtb2/ctv/combination/combination.go @@ -0,0 +1,71 @@ +// Package combination generates possible ad pod response +// based on bid response durations. It ensures that generated +// combination is satifying ad pod request configurations like +// Min Pod Duation, Maximum Pod Duration, Minimum number of ads, Maximum number of Ads. +// It also considers number of bids received for given duration +// For Example, if for 60 second duration we have 2 bids then +// then it will ensure combination contains at most 2 repeatations of 60 sec; not more than that +package combination + +import ( + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// ICombination ... +type ICombination interface { + Get() []int +} + +// Combination ... +type Combination struct { + ICombination + data []int + generator generator + config *openrtb_ext.VideoAdPod + order int // order of combination generator +} + +// NewCombination ... Generates on demand valid combinations +// Valid combinations are those who satisifies +// 1. Pod Min Max duration +// 2. minAds <= size(combination) <= maxads +// 3. If Combination contains repeatition for given duration then +// repeatitions are <= no of ads received for the duration +// +// Use Get method to start getting valid combinations +func NewCombination(buckets types.BidsBuckets, podMinDuration, podMaxDuration uint64, config *openrtb_ext.VideoAdPod) *Combination { + generator := new(generator) + durationBidsCnts := make([][2]uint64, 0) + for duration, bids := range buckets { + durationBidsCnts = append(durationBidsCnts, [2]uint64{uint64(duration), uint64(len(bids))}) + } + generator.Init(podMinDuration, podMaxDuration, config, durationBidsCnts, MaxToMin) + return &Combination{ + generator: *generator, + config: config, + } +} + +// Get next valid combination +// Retuns empty slice if all combinations are generated +func (c *Combination) Get() []int { + nextComb := c.generator.Next() + nextCombInt := make([]int, len(nextComb)) + cnt := 0 + for _, duration := range nextComb { + nextCombInt[cnt] = int(duration) + cnt++ + } + return nextCombInt +} + +const ( + // MinToMax tells combination generator to generate combinations + // starting from Min Ads to Max Ads + MinToMax = iota + + // MaxToMin tells combination generator to generate combinations + // starting from Max Ads to Min Ads + MaxToMin +) diff --git a/endpoints/openrtb2/ctv/combination/combination_test.go b/endpoints/openrtb2/ctv/combination/combination_test.go new file mode 100644 index 00000000000..d22f5518224 --- /dev/null +++ b/endpoints/openrtb2/ctv/combination/combination_test.go @@ -0,0 +1,38 @@ +package combination + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestCombination(t *testing.T) { + buckets := make(types.BidsBuckets) + + dBids := make([]*types.Bid, 0) + for i := 1; i <= 3; i++ { + bid := new(types.Bid) + bid.Duration = 10 * i + dBids = append(dBids, bid) + buckets[bid.Duration] = dBids + } + + config := new(openrtb_ext.VideoAdPod) + config.MinAds = new(int) + *config.MinAds = 2 + config.MaxAds = new(int) + *config.MaxAds = 4 + + c := NewCombination(buckets, 30, 70, config) + + for true { + comb := c.generator.Next() + if nil == comb || len(comb) == 0 { + assert.True(t, nil == comb || len(comb) == 0) + break + } + } + +} diff --git a/endpoints/openrtb2/ctv/combination/test_input/TC_1.json b/endpoints/openrtb2/ctv/combination/test_input/TC_1.json new file mode 100644 index 00000000000..498204657f2 --- /dev/null +++ b/endpoints/openrtb2/ctv/combination/test_input/TC_1.json @@ -0,0 +1,4 @@ +{ + "1" : [[14], [4]], + "2" : [[14,14],[14,4],[4,4]] +} \ No newline at end of file diff --git a/endpoints/openrtb2/ctv/constant/constant.go b/endpoints/openrtb2/ctv/constant/constant.go new file mode 100644 index 00000000000..1ea776c12a4 --- /dev/null +++ b/endpoints/openrtb2/ctv/constant/constant.go @@ -0,0 +1,53 @@ +package constant + +const ( + CTVImpressionIDSeparator = `_` + CTVImpressionIDFormat = `%v` + CTVImpressionIDSeparator + `%v` + CTVUniqueBidIDFormat = `%v-%v` + HTTPPrefix = `http` + + //VAST Constants + VASTDefaultVersion = 2.0 + VASTMaxVersion = 4.0 + VASTDefaultVersionStr = `2.0` + VASTDefaultTag = `` + VASTElement = `VAST` + VASTAdElement = `Ad` + VASTWrapperElement = `Wrapper` + VASTAdTagURIElement = `VASTAdTagURI` + VASTVersionAttribute = `version` + VASTSequenceAttribute = `sequence` + + CTVAdpod = `adpod` + CTVOffset = `offset` +) + +var ( + VASTVersionsStr = []string{"0", "1.0", "2.0", "3.0", "4.0"} +) + +// BidStatus contains bids filtering reason +type BidStatus = int64 + +const ( + //StatusOK ... + StatusOK BidStatus = 0 + //StatusWinningBid ... + StatusWinningBid BidStatus = 1 + //StatusCategoryExclusion ... + StatusCategoryExclusion BidStatus = 2 + //StatusDomainExclusion ... + StatusDomainExclusion BidStatus = 3 + //StatusDurationMismatch ... + StatusDurationMismatch BidStatus = 4 +) + +// MonitorKey provides the unique key for moniroting the algorithms +type MonitorKey string + +const ( + // CombinationGeneratorV1 ... + CombinationGeneratorV1 MonitorKey = "comp_exclusion_v1" + // CompetitiveExclusionV1 ... + CompetitiveExclusionV1 MonitorKey = "comp_exclusion_v1" +) diff --git a/endpoints/openrtb2/ctv/impressions/by_duration_range_test.go b/endpoints/openrtb2/ctv/impressions/by_duration_range_test.go new file mode 100644 index 00000000000..43cbe5675ce --- /dev/null +++ b/endpoints/openrtb2/ctv/impressions/by_duration_range_test.go @@ -0,0 +1,177 @@ +package impressions + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestGetImpressionsByDurationRanges(t *testing.T) { + type args struct { + policy openrtb_ext.OWVideoAdDurationMatchingPolicy + durations []int + maxAds int + adMinDuration int + adMaxDuration int + } + type want struct { + imps [][2]int64 + } + + tests := []struct { + name string + args args + want want + }{ + { + // do not generate impressions + name: "no_adpod_context", + args: args{}, + want: want{ + imps: [][2]int64{}, + }, + }, + { + // do not generate impressions + name: "nil_durations", + args: args{ + durations: nil, + }, + want: want{ + imps: make([][2]int64, 0), + }, + }, + { + // do not generate impressions + name: "empty_durations", + args: args{ + durations: make([]int, 0), + }, + want: want{ + imps: make([][2]int64, 0), + }, + }, + { + name: "zero_valid_durations_under_boundary", + args: args{ + policy: openrtb_ext.OWExactVideoAdDurationMatching, + durations: []int{5, 10, 15}, + maxAds: 5, + adMinDuration: 2, + adMaxDuration: 2, + }, + want: want{ + imps: [][2]int64{}, + }, + }, + { + name: "zero_valid_durations_out_of_bound", + args: args{ + policy: openrtb_ext.OWExactVideoAdDurationMatching, + durations: []int{5, 10, 15}, + maxAds: 5, + adMinDuration: 20, + adMaxDuration: 20, + }, + want: want{ + imps: [][2]int64{}, + }, + }, + { + name: "valid_durations_less_than_maxAds", + args: args{ + policy: openrtb_ext.OWExactVideoAdDurationMatching, + durations: []int{5, 10, 15, 20, 25}, + maxAds: 5, + adMinDuration: 10, + adMaxDuration: 20, + }, + want: want{ + imps: [][2]int64{ + {10, 10}, + {15, 15}, + {20, 20}, + //got repeated because of current video duration impressions are less than maxads + {10, 10}, + {15, 15}, + }, + }, + }, + { + name: "valid_durations_greater_than_maxAds", + args: args{ + policy: openrtb_ext.OWExactVideoAdDurationMatching, + durations: []int{5, 10, 15, 20, 25}, + maxAds: 2, + adMinDuration: 10, + adMaxDuration: 20, + }, + want: want{ + imps: [][2]int64{ + {10, 10}, + {15, 15}, + {20, 20}, + }, + }, + }, + { + name: "roundup_policy_valid_durations", + args: args{ + policy: openrtb_ext.OWRoundupVideoAdDurationMatching, + durations: []int{5, 10, 15, 20, 25}, + maxAds: 5, + adMinDuration: 10, + adMaxDuration: 20, + }, + want: want{ + imps: [][2]int64{ + {10, 10}, + {10, 15}, + {10, 20}, + {10, 10}, + {10, 15}, + }, + }, + }, + { + name: "roundup_policy_zero_valid_durations", + args: args{ + policy: openrtb_ext.OWRoundupVideoAdDurationMatching, + durations: []int{5, 10, 15, 20, 25}, + maxAds: 5, + adMinDuration: 30, + adMaxDuration: 30, + }, + want: want{ + imps: [][2]int64{}, + }, + }, + { + name: "roundup_policy_valid_max_ads_more_than_max_ads", + args: args{ + policy: openrtb_ext.OWRoundupVideoAdDurationMatching, + durations: []int{5, 10, 15, 20, 25}, + maxAds: 2, + adMinDuration: 10, + adMaxDuration: 20, + }, + want: want{ + imps: [][2]int64{ + {10, 10}, + {10, 15}, + {10, 20}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args := tt.args + gen := newByDurationRanges(args.policy, args.durations, args.maxAds, args.adMinDuration, args.adMaxDuration) + imps := gen.Get() + assert.Equal(t, tt.want.imps, imps) + }) + } +} diff --git a/endpoints/openrtb2/ctv/impressions/by_duration_ranges.go b/endpoints/openrtb2/ctv/impressions/by_duration_ranges.go new file mode 100644 index 00000000000..8cd5d9139ee --- /dev/null +++ b/endpoints/openrtb2/ctv/impressions/by_duration_ranges.go @@ -0,0 +1,91 @@ +package impressions + +import ( + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// byDurRangeConfig struct will be used for creating impressions object based on list of duration ranges +type byDurRangeConfig struct { + IImpressions //IImpressions interface + policy openrtb_ext.OWVideoAdDurationMatchingPolicy //duration matching algorithm round/exact + durations []int //durations list of durations in seconds used for creating impressions object + maxAds int //maxAds is number of max impressions can be created + adMinDuration int //adpod slot mininum duration + adMaxDuration int //adpod slot maximum duration +} + +// newByDurationRanges will create new object ob byDurRangeConfig for creating impressions for adpod request +func newByDurationRanges(policy openrtb_ext.OWVideoAdDurationMatchingPolicy, durations []int, + maxAds, adMinDuration, adMaxDuration int) byDurRangeConfig { + + return byDurRangeConfig{ + policy: policy, + durations: durations, + maxAds: maxAds, + adMinDuration: adMinDuration, + adMaxDuration: adMaxDuration, + } +} + +// Get function returns lists of min,max duration ranges ganerated based on durations +// it will return valid durations, duration must be within podMinDuration and podMaxDuration range +// if len(durations) < maxAds then clone valid durations from starting till we reach maxAds length +func (c *byDurRangeConfig) Get() [][2]int64 { + if len(c.durations) == 0 { + util.Logf("durations is nil. [%v] algorithm returning not generated impressions", c.Algorithm()) + return make([][2]int64, 0) + } + + isRoundupDurationMatchingPolicy := (openrtb_ext.OWRoundupVideoAdDurationMatching == c.policy) + var minDuration = -1 + var validDurations []int + + for _, dur := range c.durations { + // validate durations (adminduration <= lineitemduration <= admaxduration) (adpod adslot min and max duration) + if !(c.adMinDuration <= dur && dur <= c.adMaxDuration) { + continue // invalid duration + } + + // finding minimum duration for roundup policy, this may include valid or invalid duration + if isRoundupDurationMatchingPolicy && (minDuration == -1 || minDuration >= dur) { + minDuration = dur + } + + validDurations = append(validDurations, dur) + } + + imps := make([][2]int64, 0) + for _, dur := range validDurations { + /* + minimum value is depends on duration matching policy + openrtb_ext.OWAdPodRoundupDurationMatching (round): minduration would be min(duration) + openrtb_ext.OWAdPodExactDurationMatching (exact) or empty: minduration would be same as maxduration + */ + if isRoundupDurationMatchingPolicy { + imps = append(imps, [2]int64{int64(minDuration), int64(dur)}) + } else { + imps = append(imps, [2]int64{int64(dur), int64(dur)}) + } + } + + //calculate max ads + maxAds := c.maxAds + if len(validDurations) > maxAds { + maxAds = len(validDurations) + } + + //adding extra impressions incase of total impressions generated are less than pod max ads. + if len(imps) > 0 { + for i := 0; len(imps) < maxAds; i++ { + imps = append(imps, [2]int64{imps[i][0], imps[i][1]}) + } + } + + return imps +} + +// Algorithm returns MinMaxAlgorithm +func (c *byDurRangeConfig) Algorithm() Algorithm { + return ByDurationRanges +} diff --git a/endpoints/openrtb2/ctv/impressions/helper.go b/endpoints/openrtb2/ctv/impressions/helper.go new file mode 100644 index 00000000000..52eaa9b93e6 --- /dev/null +++ b/endpoints/openrtb2/ctv/impressions/helper.go @@ -0,0 +1,140 @@ +package impressions + +import ( + "math" + + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// newConfig initializes the generator instance +func newConfig(podMinDuration, podMaxDuration int64, vPod openrtb_ext.VideoAdPod) generator { + config := generator{} + config.totalSlotTime = new(int64) + // configure requested pod + config.requested = pod{ + podMinDuration: podMinDuration, + podMaxDuration: podMaxDuration, + slotMinDuration: int64(*vPod.MinDuration), + slotMaxDuration: int64(*vPod.MaxDuration), + minAds: int64(*vPod.MinAds), + maxAds: int64(*vPod.MaxAds), + } + + // configure internal object (FOR INTERNAL USE ONLY) + // this is used for internal computation and may contains modified values of + // slotMinDuration and slotMaxDuration in multiples of multipleOf factor + // This function will by deault intialize this pod with same values + // as of requestedPod + // There is another function newConfigWithMultipleOf, which computes and assigns + // values to this object + config.internal = internal{ + slotMinDuration: config.requested.slotMinDuration, + slotMaxDuration: config.requested.slotMaxDuration, + } + return config +} + +// newConfigWithMultipleOf initializes the generator instance +// it internally calls newConfig to obtain the generator instance +// then it computes closed to factor basedon 'multipleOf' parameter value +// and accordingly determines the Pod Min/Max and Slot Min/Max values for internal +// computation only. +func newConfigWithMultipleOf(podMinDuration, podMaxDuration int64, vPod openrtb_ext.VideoAdPod, multipleOf int64) generator { + config := newConfig(podMinDuration, podMaxDuration, vPod) + + // try to compute slot level min and max duration values in multiple of + // given number. If computed values are overlapping then prefer requested + if config.requested.slotMinDuration == config.requested.slotMaxDuration { + /*TestCase 30*/ + util.Logf("requested.SlotMinDuration = requested.SlotMaxDuration = %v\n", config.requested.slotMinDuration) + config.internal.slotMinDuration = config.requested.slotMinDuration + config.internal.slotMaxDuration = config.requested.slotMaxDuration + } else { + config.internal.slotMinDuration = getClosestFactorForMinDuration(int64(config.requested.slotMinDuration), multipleOf) + config.internal.slotMaxDuration = getClosestFactorForMaxDuration(int64(config.requested.slotMaxDuration), multipleOf) + config.internal.slotDurationComputed = true + if config.internal.slotMinDuration > config.internal.slotMaxDuration { + // computed slot min duration > computed slot max duration + // avoid overlap and prefer requested values + config.internal.slotMinDuration = config.requested.slotMinDuration + config.internal.slotMaxDuration = config.requested.slotMaxDuration + // update marker indicated slot duation values are not computed + // this required by algorithm in computeTimeForEachAdSlot function + config.internal.slotDurationComputed = false + } + } + return config +} + +// Returns true if num is multipleof second argument. False otherwise +func isMultipleOf(num, multipleOf int64) bool { + return math.Mod(float64(num), float64(multipleOf)) == 0 +} + +// Returns closest factor for num, with respect input multipleOf +// +// Example: Closest Factor of 9, in multiples of 5 is '10' +func getClosestFactor(num, multipleOf int64) int64 { + return int64(math.Round(float64(num)/float64(multipleOf)) * float64(multipleOf)) +} + +// Returns closestfactor of MinDuration, with respect to multipleOf +// If computed factor < MinDuration then it will ensure and return +// close factor >= MinDuration +func getClosestFactorForMinDuration(MinDuration int64, multipleOf int64) int64 { + closedMinDuration := getClosestFactor(MinDuration, multipleOf) + + if closedMinDuration == 0 { + return multipleOf + } + + if closedMinDuration < MinDuration { + return closedMinDuration + multipleOf + } + + return closedMinDuration +} + +// Returns closestfactor of maxduration, with respect to multipleOf +// If computed factor > maxduration then it will ensure and return +// close factor <= maxduration +func getClosestFactorForMaxDuration(maxduration, multipleOf int64) int64 { + closedMaxDuration := getClosestFactor(maxduration, multipleOf) + if closedMaxDuration == maxduration { + return maxduration + } + + // set closest maxduration closed to masduration + for i := closedMaxDuration; i <= maxduration; { + if closedMaxDuration < maxduration { + closedMaxDuration = i + multipleOf + i = closedMaxDuration + } + } + + if closedMaxDuration > maxduration { + duration := closedMaxDuration - multipleOf + if duration == 0 { + // return input value as is instead of zero to avoid NPE + return maxduration + } + return duration + } + + return closedMaxDuration +} + +// Returns Maximum number out off 2 input numbers +func max(num1, num2 int64) int64 { + + if num1 > num2 { + return num1 + } + + if num2 > num1 { + return num2 + } + // both must be equal here + return num1 +} diff --git a/endpoints/openrtb2/ctv/impressions/impression_generator.go b/endpoints/openrtb2/ctv/impressions/impression_generator.go new file mode 100644 index 00000000000..f530932cd67 --- /dev/null +++ b/endpoints/openrtb2/ctv/impressions/impression_generator.go @@ -0,0 +1,352 @@ +package impressions + +import ( + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/util" +) + +// generator contains Pod Minimum Duration, Pod Maximum Duration, Slot Minimum Duration and Slot Maximum Duration +// It holds additional attributes required by this algorithm for internal computation. +// +// It contains Slots attribute. This attribute holds the output of this algorithm +type generator struct { + IImpressions + Slots [][2]int64 // Holds Minimum and Maximum duration (in seconds) for each Ad Slot. Length indicates total number of Ad Slots/ Impressions for given Ad Pod + totalSlotTime *int64 // Total Sum of all Ad Slot durations (in seconds) + freeTime int64 // Remaining Time (in seconds) not allocated. It is compared with RequestedPodMaxDuration + slotsWithZeroTime *int64 // Indicates number of slots with zero time (starting from 1). + // requested holds all the requested information received + requested pod + // internal holds the slot duration values closed to original value and multiples of X. + // It helps in plotting impressions with duration values in multiples of given number + internal internal +} + +// pod for internal computation +// should not be used outside +type pod struct { + minAds int64 + maxAds int64 + slotMinDuration int64 + slotMaxDuration int64 + podMinDuration int64 + podMaxDuration int64 +} + +// internal (FOR INTERNAL USE ONLY) holds the computed values slot min and max duration +// in multiples of given number. It also holds slotDurationComputed flag +// if slotDurationComputed = false, it means values computed were overlapping +type internal struct { + slotMinDuration int64 + slotMaxDuration int64 + slotDurationComputed bool +} + +// Get returns the number of Ad Slots/Impression that input Ad Pod can have. +// It returns List 2D array containing following +// 1. Dimension 1 - Represents the minimum duration of an impression +// 2. Dimension 2 - Represents the maximum duration of an impression +func (config *generator) Get() [][2]int64 { + util.Logf("Pod Config with Internal Computation (using multiples of %v) = %+v\n", multipleOf, config) + totalAds := computeTotalAds(*config) + timeForEachSlot := computeTimeForEachAdSlot(*config, totalAds) + + config.Slots = make([][2]int64, totalAds) + config.slotsWithZeroTime = new(int64) + *config.slotsWithZeroTime = totalAds + util.Logf("Plotted Ad Slots / Impressions of size = %v\n", len(config.Slots)) + // iterate over total time till it is < cfg.RequestedPodMaxDuration + time := int64(0) + util.Logf("Started allocating durations to each Ad Slot / Impression\n") + fillZeroSlotsOnPriority := true + noOfZeroSlotsFilledByLastRun := int64(0) + *config.totalSlotTime = 0 + for time < config.requested.podMaxDuration { + adjustedTime, slotsFull := config.addTime(timeForEachSlot, fillZeroSlotsOnPriority) + time += adjustedTime + timeForEachSlot = computeTimeLeastValue(config.requested.podMaxDuration-time, config.requested.slotMaxDuration-timeForEachSlot) + if slotsFull { + util.Logf("All slots are full of their capacity. validating slots\n") + break + } + + // instruct for filling zero capacity slots on priority if + // 1. shouldAdjustSlotWithZeroDuration returns true + // 2. there are slots with 0 duration + // 3. there is at least ont slot with zero duration filled by last iteration + fillZeroSlotsOnPriority = false + noOfZeroSlotsFilledByLastRun = *config.slotsWithZeroTime - noOfZeroSlotsFilledByLastRun + if config.shouldAdjustSlotWithZeroDuration() && *config.slotsWithZeroTime > 0 && noOfZeroSlotsFilledByLastRun > 0 { + fillZeroSlotsOnPriority = true + } + } + util.Logf("Completed allocating durations to each Ad Slot / Impression\n") + + // validate slots + config.validateSlots() + + // log free time if present to stats server + // also check algoritm computed the no. of ads + if config.requested.podMaxDuration-time > 0 && len(config.Slots) > 0 { + config.freeTime = config.requested.podMaxDuration - time + util.Logf("TO STATS SERVER : Free Time not allocated %v sec", config.freeTime) + } + + util.Logf("\nTotal Impressions = %v, Total Allocated Time = %v sec (out of %v sec, Max Pod Duration)\n%v", len(config.Slots), *config.totalSlotTime, config.requested.podMaxDuration, config.Slots) + return config.Slots +} + +// Returns total number of Ad Slots/ impressions that the Ad Pod can have +func computeTotalAds(cfg generator) int64 { + if cfg.internal.slotMaxDuration <= 0 || cfg.internal.slotMinDuration <= 0 { + util.Logf("Either cfg.slotMaxDuration or cfg.slotMinDuration or both are <= 0. Hence, totalAds = 0") + return 0 + } + minAds := cfg.requested.podMaxDuration / cfg.internal.slotMaxDuration + maxAds := cfg.requested.podMaxDuration / cfg.internal.slotMinDuration + + util.Logf("Computed minAds = %v , maxAds = %v\n", minAds, maxAds) + + totalAds := max(minAds, maxAds) + util.Logf("Computed max(minAds, maxAds) = totalAds = %v\n", totalAds) + + if totalAds < cfg.requested.minAds { + totalAds = cfg.requested.minAds + util.Logf("Computed totalAds < requested minAds (%v). Hence, setting totalAds = minAds = %v\n", cfg.requested.minAds, totalAds) + } + if totalAds > cfg.requested.maxAds { + totalAds = cfg.requested.maxAds + util.Logf("Computed totalAds > requested maxAds (%v). Hence, setting totalAds = maxAds = %v\n", cfg.requested.maxAds, totalAds) + } + util.Logf("Computed Final totalAds = %v [%v <= %v <= %v]\n", totalAds, cfg.requested.minAds, totalAds, cfg.requested.maxAds) + return totalAds +} + +// Returns duration in seconds that can be allocated to each Ad Slot +// Accepts cfg containing algorithm configurations and totalAds containing Total number of +// Ad Slots / Impressions that the Ad Pod can have. +func computeTimeForEachAdSlot(cfg generator, totalAds int64) int64 { + // Compute time for each ad + if totalAds <= 0 { + util.Logf("totalAds = 0, Hence timeForEachSlot = 0") + return 0 + } + timeForEachSlot := cfg.requested.podMaxDuration / totalAds + + util.Logf("Computed timeForEachSlot = %v (podMaxDuration/totalAds) (%v/%v)\n", timeForEachSlot, cfg.requested.podMaxDuration, totalAds) + + if timeForEachSlot < cfg.internal.slotMinDuration { + timeForEachSlot = cfg.internal.slotMinDuration + util.Logf("Computed timeForEachSlot < requested slotMinDuration (%v). Hence, setting timeForEachSlot = slotMinDuration = %v\n", cfg.internal.slotMinDuration, timeForEachSlot) + } + + if timeForEachSlot > cfg.internal.slotMaxDuration { + timeForEachSlot = cfg.internal.slotMaxDuration + util.Logf("Computed timeForEachSlot > requested slotMaxDuration (%v). Hence, setting timeForEachSlot = slotMaxDuration = %v\n", cfg.internal.slotMaxDuration, timeForEachSlot) + } + + // Case - Exact slot duration is given. No scope for finding multiples + // of given number. Prefer to return computed timeForEachSlot + // In such case timeForEachSlot no necessarily to be multiples of given number + if cfg.requested.slotMinDuration == cfg.requested.slotMaxDuration { + util.Logf("requested.slotMinDuration = requested.slotMaxDuration = %v. Hence, not computing multiples of %v value.", cfg.requested.slotMaxDuration, multipleOf) + return timeForEachSlot + } + + // Case II - timeForEachSlot*totalAds > podmaxduration + // In such case prefer to return cfg.podMaxDuration / totalAds + // In such case timeForEachSlot no necessarily to be multiples of given number + if (timeForEachSlot * totalAds) > cfg.requested.podMaxDuration { + util.Logf("timeForEachSlot*totalAds (%v) > cfg.requested.podMaxDuration (%v) ", timeForEachSlot*totalAds, cfg.requested.podMaxDuration) + util.Logf("Hence, not computing multiples of %v value.", multipleOf) + // need that division again + return cfg.requested.podMaxDuration / totalAds + } + + // ensure timeForEachSlot is multipleof given number + if cfg.internal.slotDurationComputed && !isMultipleOf(timeForEachSlot, multipleOf) { + // get close to value of multiple + // here we muse get either cfg.SlotMinDuration or cfg.SlotMaxDuration + // these values are already pre-computed in multiples of given number + timeForEachSlot = getClosestFactor(timeForEachSlot, multipleOf) + util.Logf("Computed closet factor %v, in multiples of %v for timeForEachSlot\n", timeForEachSlot, multipleOf) + } + util.Logf("Computed Final timeForEachSlot = %v [%v <= %v <= %v]\n", timeForEachSlot, cfg.requested.slotMinDuration, timeForEachSlot, cfg.requested.slotMaxDuration) + return timeForEachSlot +} + +// Checks if multipleOf can be used as least time value +// this will ensure eack slot to maximize its time if possible +// if multipleOf can not be used as least value then default input value is returned as is +// accepts time containing, which least value to be computed. +// leastTimeRequiredByEachSlot - indicates the mimimum time that any slot can accept (UOE-5268) +// Returns the least value based on multiple of X +func computeTimeLeastValue(time int64, leastTimeRequiredByEachSlot int64) int64 { + // time if Testcase#6 + // 1. multiple of x - get smallest factor N of multiple of x for time + // 2. not multiple of x - try to obtain smallet no N multipe of x + // ensure N <= timeForEachSlot + leastFactor := multipleOf + if leastFactor < time { + time = leastFactor + } + + // case: check if slots are looking for time < leastFactor + // UOE-5268 + if leastTimeRequiredByEachSlot > 0 && leastTimeRequiredByEachSlot < time { + time = leastTimeRequiredByEachSlot + } + + return time +} + +// Validate the algorithm computations +// 1. Verifies if 2D slice containing Min duration and Max duration values are non-zero +// 2. Idenfies the Ad Slots / Impressions with either Min Duration or Max Duration or both +// having zero value and removes it from 2D slice +// 3. Ensures Minimum Pod duration <= TotalSlotTime <= Maximum Pod Duration +// +// if any validation fails it removes all the alloated slots and makes is of size 0 +// and sets the freeTime value as RequestedPodMaxDuration +func (config *generator) validateSlots() { + + // default return value if validation fails + emptySlots := make([][2]int64, 0) + if len(config.Slots) == 0 { + return + } + + returnEmptySlots := false + + // check slot with 0 values + // remove them from config.Slots + emptySlotCount := 0 + for index, slot := range config.Slots { + if slot[0] == 0 || slot[1] == 0 { + util.Logf("WARNING:Slot[%v][%v] is having 0 duration\n", index, slot) + emptySlotCount++ + continue + } + + // check slot boundaries + if slot[1] < config.requested.slotMinDuration || slot[1] > config.requested.slotMaxDuration { + util.Logf("ERROR: Slot%v Duration %v sec is out of either requested.slotMinDuration (%v) or requested.slotMaxDuration (%v)\n", index, slot[1], config.requested.slotMinDuration, config.requested.slotMaxDuration) + returnEmptySlots = true + break + } + } + + // remove empty slot + if emptySlotCount > 0 { + optimizedSlots := make([][2]int64, len(config.Slots)-emptySlotCount) + for index, slot := range config.Slots { + if slot[0] == 0 || slot[1] == 0 { + } else { + optimizedSlots[index][0] = slot[0] + optimizedSlots[index][1] = slot[1] + } + } + config.Slots = optimizedSlots + util.Logf("Removed %v empty slots\n", emptySlotCount) + } + + if int64(len(config.Slots)) < config.requested.minAds || int64(len(config.Slots)) > config.requested.maxAds { + util.Logf("ERROR: slotSize %v is either less than Min Ads (%v) or greater than Max Ads (%v)\n", len(config.Slots), config.requested.minAds, config.requested.maxAds) + returnEmptySlots = true + } + + // ensure if min pod duration = max pod duration + // config.TotalSlotTime = pod duration + if config.requested.podMinDuration == config.requested.podMaxDuration && *config.totalSlotTime != config.requested.podMaxDuration { + util.Logf("ERROR: Total Slot Duration %v sec is not matching with Total Pod Duration %v sec\n", *config.totalSlotTime, config.requested.podMaxDuration) + returnEmptySlots = true + } + + // ensure slot duration lies between requested min pod duration and requested max pod duration + // Testcase #15 + if *config.totalSlotTime < config.requested.podMinDuration || *config.totalSlotTime > config.requested.podMaxDuration { + util.Logf("ERROR: Total Slot Duration %v sec is either less than Requested Pod Min Duration (%v sec) or greater than Requested Pod Max Duration (%v sec)\n", *config.totalSlotTime, config.requested.podMinDuration, config.requested.podMaxDuration) + returnEmptySlots = true + } + + if returnEmptySlots { + config.Slots = emptySlots + config.freeTime = config.requested.podMaxDuration + } +} + +// Adds time to possible slots and returns total added time +// +// Checks following for each Ad Slot +// 1. Can Ad Slot adjust the input time +// 2. If addition of new time to any slot not exeeding Total Pod Max Duration +// +// Performs the following operations +// 1. Populates Minimum duration slot[][0] - Either Slot Minimum Duration or Actual Slot Time computed +// 2. Populates Maximum duration slot[][1] - Always actual Slot Time computed +// 3. Counts the number of Ad Slots / Impressons full with duration capacity. If all Ad Slots / Impressions +// are full of capacity it returns true as second return argument, indicating all slots are full with capacity +// 4. Keeps track of TotalSlotDuration when each new time is added to the Ad Slot +// 5. Keeps track of difference between computed PodMaxDuration and RequestedPodMaxDuration (TestCase #16) and used in step #2 above +// +// Returns argument 1 indicating total time adusted, argument 2 whether all slots are full of duration capacity +func (config generator) addTime(timeForEachSlot int64, fillZeroSlotsOnPriority bool) (int64, bool) { + time := int64(0) + + // iterate over each ad + slotCountFullWithCapacity := 0 + for ad := int64(0); ad < int64(len(config.Slots)); ad++ { + + slot := &config.Slots[ad] + // check + // 1. time(slot(0)) <= config.SlotMaxDuration + // 2. if adding new time to slot0 not exeeding config.SlotMaxDuration + // 3. if sum(slot time) + timeForEachSlot <= config.RequestedPodMaxDuration + canAdjustTime := (slot[1]+timeForEachSlot) <= config.requested.slotMaxDuration && (slot[1]+timeForEachSlot) >= config.requested.slotMinDuration + totalSlotTimeWithNewTimeLessThanRequestedPodMaxDuration := *config.totalSlotTime+timeForEachSlot <= config.requested.podMaxDuration + + // if fillZeroSlotsOnPriority= true ensure current slot value = 0 + allowCurrentSlot := !fillZeroSlotsOnPriority || (fillZeroSlotsOnPriority && slot[1] == 0) + if slot[1] <= config.internal.slotMaxDuration && canAdjustTime && totalSlotTimeWithNewTimeLessThanRequestedPodMaxDuration && allowCurrentSlot { + slot[0] += timeForEachSlot + + // if we are adjusting the free time which will match up with config.RequestedPodMaxDuration + // then set config.SlotMinDuration as min value for this slot + // TestCase #16 + //if timeForEachSlot == maxPodDurationMatchUpTime { + if timeForEachSlot < multipleOf { + // override existing value of slot[0] here + slot[0] = config.requested.slotMinDuration + } + + // check if this slot duration was zero + if slot[1] == 0 { + // decrememt config.slotsWithZeroTime as we added some time for this slot + *config.slotsWithZeroTime-- + } + + slot[1] += timeForEachSlot + *config.totalSlotTime += timeForEachSlot + time += timeForEachSlot + util.Logf("Slot %v = Added %v sec (New Time = %v)\n", ad, timeForEachSlot, slot[1]) + } + // check slot capabity + // !canAdjustTime - TestCase18 + // UOE-5268 - Check with Requested Slot Max Duration + if slot[1] == config.requested.slotMaxDuration || !canAdjustTime { + // slot is full + slotCountFullWithCapacity++ + } + } + util.Logf("adjustedTime = %v\n ", time) + return time, slotCountFullWithCapacity == len(config.Slots) +} + +// shouldAdjustSlotWithZeroDuration - returns if slot with zero durations should be filled +// Currently it will return true in following condition +// cfg.minAds = cfg.maxads (i.e. Exact number of ads are required) +func (config generator) shouldAdjustSlotWithZeroDuration() bool { + if config.requested.minAds == config.requested.maxAds { + return true + } + return false +} diff --git a/endpoints/openrtb2/ctv/impressions/impressions.go b/endpoints/openrtb2/ctv/impressions/impressions.go new file mode 100644 index 00000000000..bfb3f6b687b --- /dev/null +++ b/endpoints/openrtb2/ctv/impressions/impressions.go @@ -0,0 +1,116 @@ +// Package impressions provides various algorithms to get the number of impressions +// along with minimum and maximum duration of each impression. +// It uses Ad pod request for it +package impressions + +import ( + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// Algorithm indicates type of algorithms supported +// Currently it supports +// 1. MaximizeForDuration +// 2. MinMaxAlgorithm +type Algorithm int + +const ( + // MaximizeForDuration algorithm tends towards Ad Pod Maximum Duration, Ad Slot Maximum Duration + // and Maximum number of Ads. Accordingly it computes the number of impressions + MaximizeForDuration Algorithm = iota + // MinMaxAlgorithm algorithm ensures all possible impression breaks are plotted by considering + // minimum as well as maxmimum durations and ads received in the ad pod request. + // It computes number of impressions with following steps + // 1. Passes input configuration as it is (Equivalent of MaximizeForDuration algorithm) + // 2. Ad Pod Duration = Ad Pod Max Duration, Number of Ads = max ads + // 3. Ad Pod Duration = Ad Pod Max Duration, Number of Ads = min ads + // 4. Ad Pod Duration = Ad Pod Min Duration, Number of Ads = max ads + // 5. Ad Pod Duration = Ad Pod Min Duration, Number of Ads = min ads + MinMaxAlgorithm + // ByDurationRanges algorithm plots the impression objects based on expected video duration + // ranges reveived in the input prebid-request. Based on duration matching policy + // it will generate the impression objects. in case 'exact' duration matching impression + // min duration = max duration. In case 'round up' this algorithm will not be executed.Instead + ByDurationRanges +) + +// MonitorKey provides the unique key for moniroting the impressions algorithm +var MonitorKey = map[Algorithm]string{ + MaximizeForDuration: `a1_max`, + MinMaxAlgorithm: `a2_min_max`, + ByDurationRanges: `a3_duration`, +} + +// Value use to compute Ad Slot Durations and Pod Durations for internal computation +// Right now this value is set to 5, based on passed data observations +// Observed that typically video impression contains contains minimum and maximum duration in multiples of 5 +var multipleOf = int64(5) + +// IImpressions ... +type IImpressions interface { + Get() [][2]int64 + Algorithm() Algorithm // returns algorithm used for computing number of impressions +} + +// NewImpressions generate object of impression generator +// based on input algorithm type +// if invalid algorithm type is passed, it returns default algorithm which will compute +// impressions based on minimum ad slot duration +func NewImpressions(podMinDuration, podMaxDuration int64, reqAdPod *openrtb_ext.ExtRequestAdPod, vPod *openrtb_ext.VideoAdPod, algorithm Algorithm) IImpressions { + switch algorithm { + case MaximizeForDuration: + util.Logf("Selected ImpGen Algorithm - 'MaximizeForDuration'") + g := newMaximizeForDuration(podMinDuration, podMaxDuration, *vPod) + return &g + + case MinMaxAlgorithm: + util.Logf("Selected ImpGen Algorithm - 'MinMaxAlgorithm'") + g := newMinMaxAlgorithm(podMinDuration, podMaxDuration, *vPod) + return &g + + case ByDurationRanges: + util.Logf("Selected ImpGen Algorithm - 'ByDurationRanges'") + + g := newByDurationRanges(reqAdPod.VideoAdDurationMatching, reqAdPod.VideoAdDuration, + int(*vPod.MaxAds), + *vPod.MinDuration, *vPod.MaxDuration) + + return &g + } + + // return default algorithm with slot durations set to minimum slot duration + util.Logf("Selected 'DefaultAlgorithm'") + defaultGenerator := newConfig(podMinDuration, podMinDuration, openrtb_ext.VideoAdPod{ + MinAds: vPod.MinAds, + MaxAds: vPod.MaxAds, + MinDuration: vPod.MinDuration, + MaxDuration: vPod.MinDuration, // sending slot minduration as max duration + }) + return &defaultGenerator +} + +// SelectAlgorithm is factory function which will return valid Algorithm based on adpod parameters +// Return Value: +// - MinMaxAlgorithm (default) +// - ByDurationRanges: if reqAdPod extension has VideoAdDuration and VideoAdDurationMatchingPolicy is "exact" algorithm +func SelectAlgorithm(reqAdPod *openrtb_ext.ExtRequestAdPod) Algorithm { + if nil != reqAdPod { + if len(reqAdPod.VideoAdDuration) > 0 && + (openrtb_ext.OWExactVideoAdDurationMatching == reqAdPod.VideoAdDurationMatching || openrtb_ext.OWRoundupVideoAdDurationMatching == reqAdPod.VideoAdDurationMatching) { + return ByDurationRanges + } + } + return MinMaxAlgorithm +} + +// Duration indicates the position +// where the required min or max duration value can be found +// within given impression object +type Duration int + +const ( + // MinDuration represents index value where we can get minimum duration of given impression object + MinDuration Duration = iota + // MaxDuration represents index value where we can get maximum duration of given impression object + MaxDuration +) diff --git a/endpoints/openrtb2/ctv/impressions/impressions_test.go b/endpoints/openrtb2/ctv/impressions/impressions_test.go new file mode 100644 index 00000000000..6b185d11312 --- /dev/null +++ b/endpoints/openrtb2/ctv/impressions/impressions_test.go @@ -0,0 +1,147 @@ +// Package impressions provides various algorithms to get the number of impressions +// along with minimum and maximum duration of each impression. +// It uses Ad pod request for it +package impressions + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestSelectAlgorithm(t *testing.T) { + type args struct { + reqAdPod *openrtb_ext.ExtRequestAdPod + } + tests := []struct { + name string + args args + want Algorithm + }{ + { + name: "default", + args: args{}, + want: MinMaxAlgorithm, + }, + { + name: "missing_videoAdDuration", + args: args{reqAdPod: &openrtb_ext.ExtRequestAdPod{}}, + want: MinMaxAlgorithm, + }, + { + name: "roundup_matching_algo", + args: args{reqAdPod: &openrtb_ext.ExtRequestAdPod{ + VideoAdDuration: []int{15, 20}, + VideoAdDurationMatching: openrtb_ext.OWRoundupVideoAdDurationMatching, + }}, + want: ByDurationRanges, + }, + { + name: "exact_matching_algo", + args: args{reqAdPod: &openrtb_ext.ExtRequestAdPod{ + VideoAdDuration: []int{15, 20}, + VideoAdDurationMatching: openrtb_ext.OWExactVideoAdDurationMatching, + }}, + want: ByDurationRanges, + }, + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := SelectAlgorithm(tt.args.reqAdPod) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestNewImpressions(t *testing.T) { + intPtr := func(v int) *int { return &v } + + type args struct { + podMinDuration int64 + podMaxDuration int64 + reqAdPod *openrtb_ext.ExtRequestAdPod + vPod *openrtb_ext.VideoAdPod + algorithm Algorithm + } + tests := []struct { + name string + args args + want Algorithm + }{ + { + name: "Default-MaximizeForDuration", + args: args{ + podMinDuration: 15, + podMaxDuration: 90, + reqAdPod: &openrtb_ext.ExtRequestAdPod{}, + vPod: &openrtb_ext.VideoAdPod{ + MinAds: intPtr(1), + MaxAds: intPtr(2), + MinDuration: intPtr(5), + MaxDuration: intPtr(10), + }, + algorithm: Algorithm(-1), + }, + want: MaximizeForDuration, + }, + { + name: "MaximizeForDuration", + args: args{ + podMinDuration: 15, + podMaxDuration: 90, + reqAdPod: &openrtb_ext.ExtRequestAdPod{}, + vPod: &openrtb_ext.VideoAdPod{ + MinAds: intPtr(1), + MaxAds: intPtr(2), + MinDuration: intPtr(5), + MaxDuration: intPtr(10), + }, + algorithm: MaximizeForDuration, + }, + want: MaximizeForDuration, + }, + { + name: "MinMaxAlgorithm", + args: args{ + podMinDuration: 15, + podMaxDuration: 90, + reqAdPod: &openrtb_ext.ExtRequestAdPod{}, + vPod: &openrtb_ext.VideoAdPod{ + MinAds: intPtr(1), + MaxAds: intPtr(2), + MinDuration: intPtr(5), + MaxDuration: intPtr(10), + }, + algorithm: MinMaxAlgorithm, + }, + want: MinMaxAlgorithm, + }, + { + name: "ByDurationRanges", + args: args{ + podMinDuration: 15, + podMaxDuration: 90, + reqAdPod: &openrtb_ext.ExtRequestAdPod{ + VideoAdDuration: []int{10, 15}, + }, + vPod: &openrtb_ext.VideoAdPod{ + MinAds: intPtr(1), + MaxAds: intPtr(2), + MinDuration: intPtr(5), + MaxDuration: intPtr(10), + }, + algorithm: ByDurationRanges, + }, + want: ByDurationRanges, + }, + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NewImpressions(tt.args.podMinDuration, tt.args.podMaxDuration, tt.args.reqAdPod, tt.args.vPod, tt.args.algorithm) + assert.Equal(t, tt.want, got.Algorithm()) + }) + } +} diff --git a/endpoints/openrtb2/ctv/impressions/maximize_for_duration.go b/endpoints/openrtb2/ctv/impressions/maximize_for_duration.go new file mode 100644 index 00000000000..08c14cbe2b9 --- /dev/null +++ b/endpoints/openrtb2/ctv/impressions/maximize_for_duration.go @@ -0,0 +1,25 @@ +package impressions + +import ( + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// newMaximizeForDuration Constucts the generator object from openrtb_ext.VideoAdPod +// It computes durations for Ad Slot and Ad Pod in multiple of X +func newMaximizeForDuration(podMinDuration, podMaxDuration int64, vPod openrtb_ext.VideoAdPod) generator { + config := newConfigWithMultipleOf(podMinDuration, podMaxDuration, vPod, multipleOf) + + util.Logf("Computed podMinDuration = %v in multiples of %v (requestedPodMinDuration = %v)\n", config.requested.podMinDuration, multipleOf, config.requested.podMinDuration) + util.Logf("Computed podMaxDuration = %v in multiples of %v (requestedPodMaxDuration = %v)\n", config.requested.podMaxDuration, multipleOf, config.requested.podMaxDuration) + util.Logf("Computed slotMinDuration = %v in multiples of %v (requestedSlotMinDuration = %v)\n", config.internal.slotMinDuration, multipleOf, config.requested.slotMinDuration) + util.Logf("Computed slotMaxDuration = %v in multiples of %v (requestedSlotMaxDuration = %v)\n", config.internal.slotMaxDuration, multipleOf, *vPod.MaxDuration) + util.Logf("Requested minAds = %v\n", config.requested.minAds) + util.Logf("Requested maxAds = %v\n", config.requested.maxAds) + return config +} + +// Algorithm returns MaximizeForDuration +func (config generator) Algorithm() Algorithm { + return MaximizeForDuration +} diff --git a/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go b/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go new file mode 100644 index 00000000000..9bcad7cf1d6 --- /dev/null +++ b/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go @@ -0,0 +1,465 @@ +package impressions + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/impressions/testdata" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +type TestAdPod struct { + vPod openrtb_ext.VideoAdPod + podMinDuration int64 + podMaxDuration int64 +} + +type expected struct { + impressionCount int + // Time remaining after ad breaking is done + // if no ad breaking i.e. 0 then freeTime = pod.maxduration + freeTime int64 + adSlotTimeInSec []int64 + + // close bounds + closedMinDuration int64 // pod + closedMaxDuration int64 // pod + closedSlotMinDuration int64 // ad slot + closedSlotMaxDuration int64 // ad slot +} + +var impressionsTests = []struct { + scenario string // Testcase scenario + out expected // Testcase execpted output +}{ + {scenario: "TC2", out: expected{ + impressionCount: 6, + freeTime: 0.0, + closedMinDuration: 5, + closedMaxDuration: 90, + closedSlotMinDuration: 15, + closedSlotMaxDuration: 15, + }}, + {scenario: "TC3", out: expected{ + impressionCount: 4, + freeTime: 30.0, closedMinDuration: 5, + closedMaxDuration: 90, + closedSlotMinDuration: 15, + closedSlotMaxDuration: 15, + }}, + {scenario: "TC4", out: expected{ + impressionCount: 1, + freeTime: 0.0, closedMinDuration: 5, + closedMaxDuration: 15, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 15, + }}, + {scenario: "TC5", out: expected{ + impressionCount: 2, + freeTime: 0.0, closedMinDuration: 5, + closedMaxDuration: 15, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 15, + }}, + {scenario: "TC6", out: expected{ + impressionCount: 8, + freeTime: 0.0, closedMinDuration: 5, + closedMaxDuration: 90, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 15, + }}, + {scenario: "TC7", out: expected{ + impressionCount: 1, + freeTime: 15.0, closedMinDuration: 15, + closedMaxDuration: 30, + closedSlotMinDuration: 10, + closedSlotMaxDuration: 15, + }}, + {scenario: "TC8", out: expected{ + impressionCount: 3, + freeTime: 0.0, closedMinDuration: 35, + closedMaxDuration: 35, + closedSlotMinDuration: 10, + closedSlotMaxDuration: 35, + }}, + {scenario: "TC9", out: expected{ + impressionCount: 0, + freeTime: 35, closedMinDuration: 35, + closedMaxDuration: 35, + closedSlotMinDuration: 10, + closedSlotMaxDuration: 35, + }}, + {scenario: "TC10", out: expected{ + impressionCount: 6, + freeTime: 0.0, closedMinDuration: 35, + closedMaxDuration: 65, + closedSlotMinDuration: 10, + closedSlotMaxDuration: 35, + }}, + {scenario: "TC11", out: expected{ + impressionCount: 0, //7, + freeTime: 0, closedMinDuration: 35, + closedMaxDuration: 65, + closedSlotMinDuration: 10, + closedSlotMaxDuration: 35, + }}, + {scenario: "TC12", out: expected{ + impressionCount: 10, + freeTime: 0.0, closedMinDuration: 100, + closedMaxDuration: 100, + closedSlotMinDuration: 10, + closedSlotMaxDuration: 35, + }}, + {scenario: "TC13", out: expected{ + impressionCount: 0, + freeTime: 60, closedMinDuration: 60, + closedMaxDuration: 60, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 5, + }}, + {scenario: "TC14", out: expected{ + impressionCount: 6, + freeTime: 6, closedMinDuration: 30, + closedMaxDuration: 60, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 5, + }}, + {scenario: "TC15", out: expected{ + impressionCount: 5, + freeTime: 15, closedMinDuration: 30, + closedMaxDuration: 60, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 5, + }}, + {scenario: "TC16", out: expected{ + impressionCount: 13, + freeTime: 0, closedMinDuration: 126, + closedMaxDuration: 126, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 10, + }}, + {scenario: "TC17", out: expected{ + impressionCount: 13, + freeTime: 0, closedMinDuration: 130, + closedMaxDuration: 125, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 10, + }}, + {scenario: "TC18", out: expected{ + impressionCount: 0, + freeTime: 125, closedMinDuration: 125, + closedMaxDuration: 125, + closedSlotMinDuration: 4, + closedSlotMaxDuration: 4, + }}, + {scenario: "TC19", out: expected{ + impressionCount: 0, + freeTime: 90, closedMinDuration: 90, + closedMaxDuration: 90, + closedSlotMinDuration: 7, // overlapping case. Hence as is + closedSlotMaxDuration: 9, + }}, + {scenario: "TC20", out: expected{ + impressionCount: 9, + freeTime: 0, closedMinDuration: 90, + closedMaxDuration: 90, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 10, + }}, + {scenario: "TC21", out: expected{ + impressionCount: 9, + freeTime: 89, closedMinDuration: 5, + closedMaxDuration: 170, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 5, + }}, + {scenario: "TC23", out: expected{ + impressionCount: 12, + freeTime: 0, closedMinDuration: 120, + closedMaxDuration: 120, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 15, + }}, + {scenario: "TC24", out: expected{ + impressionCount: 2, + freeTime: 0, closedMinDuration: 134, + closedMaxDuration: 134, + closedSlotMinDuration: 60, + closedSlotMaxDuration: 90, + }}, + {scenario: "TC25", out: expected{ + impressionCount: 2, + freeTime: 0, + + closedMinDuration: 88, + closedMaxDuration: 88, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 80, + }}, + {scenario: "TC26", out: expected{ + impressionCount: 2, + freeTime: 0, + + closedMinDuration: 90, + closedMaxDuration: 90, + closedSlotMinDuration: 45, + closedSlotMaxDuration: 45, + }}, + {scenario: "TC27", out: expected{ + impressionCount: 3, + freeTime: 0, + + closedMinDuration: 5, + closedMaxDuration: 90, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 45, + }}, + {scenario: "TC28", out: expected{ + impressionCount: 6, + freeTime: 0, + + closedMinDuration: 5, + closedMaxDuration: 180, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 90, + }}, + {scenario: "TC29", out: expected{ + impressionCount: 3, + freeTime: 0, closedMinDuration: 5, + closedMaxDuration: 65, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 35, + }}, + {scenario: "TC30", out: expected{ + impressionCount: 3, + freeTime: 123, closedMinDuration: 123, + closedMaxDuration: 123, + closedSlotMinDuration: 34, + closedSlotMaxDuration: 34, + }}, + {scenario: "TC31", out: expected{ + impressionCount: 3, + freeTime: 123, closedMinDuration: 123, + closedMaxDuration: 123, + closedSlotMinDuration: 31, + closedSlotMaxDuration: 31, + }}, {scenario: "TC32", out: expected{ + impressionCount: 0, + freeTime: 134, closedMinDuration: 134, + closedMaxDuration: 134, + closedSlotMinDuration: 63, + closedSlotMaxDuration: 63, + }}, + {scenario: "TC33", out: expected{ + impressionCount: 4, + freeTime: 0, closedMinDuration: 147, + closedMaxDuration: 147, + closedSlotMinDuration: 30, + closedSlotMaxDuration: 60, + }}, + {scenario: "TC34", out: expected{ + impressionCount: 3, + freeTime: 12, closedMinDuration: 90, + closedMaxDuration: 100, + closedSlotMinDuration: 30, + closedSlotMaxDuration: 30, + }}, {scenario: "TC35", out: expected{ + impressionCount: 0, + freeTime: 102, closedMinDuration: 90, + closedMaxDuration: 100, + closedSlotMinDuration: 30, + closedSlotMaxDuration: 40, + }}, {scenario: "TC36", out: expected{ + impressionCount: 2, + freeTime: 0, closedMinDuration: 90, + closedMaxDuration: 90, + closedSlotMinDuration: 45, + closedSlotMaxDuration: 45, + }}, {scenario: "TC37", out: expected{ + impressionCount: 2, + freeTime: 0, closedMinDuration: 10, + closedMaxDuration: 45, + closedSlotMinDuration: 20, + closedSlotMaxDuration: 45, + }}, {scenario: "TC38", out: expected{ + impressionCount: 0, + freeTime: 0, closedMinDuration: 90, + closedMaxDuration: 90, + closedSlotMinDuration: 20, + closedSlotMaxDuration: 45, + }}, {scenario: "TC39", out: expected{ + impressionCount: 4, + freeTime: 0, closedMinDuration: 60, + closedMaxDuration: 90, + closedSlotMinDuration: 20, + closedSlotMaxDuration: 45, + }}, {scenario: "TC40", out: expected{ + impressionCount: 10, + freeTime: 0, closedMinDuration: 95, + closedMaxDuration: 95, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 45, + }}, {scenario: "TC41", out: expected{ + impressionCount: 0, + freeTime: 123, closedMinDuration: 95, + closedMaxDuration: 120, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 45, + }}, {scenario: "TC42", out: expected{ + impressionCount: 1, + freeTime: 0, closedMinDuration: 1, + closedMaxDuration: 1, + closedSlotMinDuration: 1, + closedSlotMaxDuration: 1, + }}, {scenario: "TC43", out: expected{ + impressionCount: 0, + freeTime: 2, closedMinDuration: 2, + closedMaxDuration: 2, + closedSlotMinDuration: 2, + closedSlotMaxDuration: 2, + }}, {scenario: "TC44", out: expected{ + impressionCount: 0, + freeTime: 0, closedMinDuration: 0, + closedMaxDuration: 0, + closedSlotMinDuration: 0, + closedSlotMaxDuration: 0, + }}, {scenario: "TC45", out: expected{ + impressionCount: 0, + freeTime: 0, closedMinDuration: 5, + closedMaxDuration: -5, + closedSlotMinDuration: -3, // overlapping hence will as is + closedSlotMaxDuration: -4, + }}, {scenario: "TC46", out: expected{ + impressionCount: 0, + freeTime: 0, closedMinDuration: -1, + closedMaxDuration: -1, + closedSlotMinDuration: -1, + closedSlotMaxDuration: -1, + }}, {scenario: "TC47", out: expected{ + impressionCount: 1, + freeTime: 0, closedMinDuration: 6, + closedMaxDuration: 6, + closedSlotMinDuration: 6, + closedSlotMaxDuration: 6, + }}, {scenario: "TC48", out: expected{ + impressionCount: 2, + freeTime: 0, closedMinDuration: 12, + closedMaxDuration: 12, + closedSlotMinDuration: 6, + closedSlotMaxDuration: 6, + }}, {scenario: "TC49", out: expected{ + impressionCount: 0, + freeTime: 12, closedMinDuration: 12, + closedMaxDuration: 12, + closedSlotMinDuration: 7, + closedSlotMaxDuration: 7, + }}, {scenario: "TC50", out: expected{ + impressionCount: 0, + freeTime: 0, closedMinDuration: 1, + closedMaxDuration: 1, + closedSlotMinDuration: 1, + closedSlotMaxDuration: 1, + }}, {scenario: "TC51", out: expected{ + impressionCount: 3, + freeTime: 4, closedMinDuration: 35, + closedMaxDuration: 40, + closedSlotMinDuration: 11, + closedSlotMaxDuration: 13, + }}, + {scenario: "TC52", out: expected{ + impressionCount: 3, + freeTime: 0, closedMinDuration: 70, + closedMaxDuration: 70, + closedSlotMinDuration: 15, + closedSlotMaxDuration: 15, + }}, {scenario: "TC53", out: expected{ + impressionCount: 3, + freeTime: 0, closedMinDuration: 126, + closedMaxDuration: 126, + closedSlotMinDuration: 5, + closedSlotMaxDuration: 20, + }}, {scenario: "TC55", out: expected{ + impressionCount: 6, + freeTime: 2, closedMinDuration: 1, + closedMaxDuration: 74, + closedSlotMinDuration: 12, + closedSlotMaxDuration: 12, + }}, {scenario: "TC56", out: expected{ + impressionCount: 1, + freeTime: 0, closedMinDuration: 126, + closedMaxDuration: 126, + closedSlotMinDuration: 126, + closedSlotMaxDuration: 126, + }}, {scenario: "TC57", out: expected{ + impressionCount: 1, + freeTime: 0, closedMinDuration: 126, + closedMaxDuration: 126, + closedSlotMinDuration: 126, + closedSlotMaxDuration: 126, + }}, {scenario: "TC58", out: expected{ + impressionCount: 4, + freeTime: 0, closedMinDuration: 30, + closedMaxDuration: 90, + closedSlotMinDuration: 15, + closedSlotMaxDuration: 45, + }}, + {scenario: "TC59", out: expected{ + impressionCount: 1, + freeTime: 45, closedMinDuration: 30, + closedMaxDuration: 90, + closedSlotMinDuration: 15, + closedSlotMaxDuration: 45, + }}, +} + +func TestGetImpressionsA1(t *testing.T) { + for _, impTest := range impressionsTests { + t.Run(impTest.scenario, func(t *testing.T) { + in := testdata.Input[impTest.scenario] + p := newTestPod(int64(in[0]), int64(in[1]), in[2], in[3], in[4], in[5]) + + cfg := newMaximizeForDuration(p.podMinDuration, p.podMaxDuration, p.vPod) + imps := cfg.Get() + expected := impTest.out + expectedImpressionBreak := testdata.Scenario[impTest.scenario].MaximizeForDuration + // assert.Equal(t, expected.impressionCount, len(pod.Slots), "expected impression count = %v . But Found %v", expectedImpressionCount, len(pod.Slots)) + assert.Equal(t, expected.freeTime, cfg.freeTime, "expected Free Time = %v . But Found %v", expected.freeTime, cfg.freeTime) + // assert.Equal(t, expected.closedMinDuration, cfg.requested.podMinDuration, "expected closedMinDuration= %v . But Found %v", expected.closedMinDuration, cfg.requested.podMinDuration) + // assert.Equal(t, expected.closedMaxDuration, cfg.requested.podMaxDuration, "expected closedMinDuration= %v . But Found %v", expected.closedMaxDuration, cfg.requested.podMaxDuration) + assert.Equal(t, expected.closedSlotMinDuration, cfg.internal.slotMinDuration, "expected closedSlotMinDuration= %v . But Found %v", expected.closedSlotMinDuration, cfg.internal.slotMinDuration) + assert.Equal(t, expected.closedSlotMaxDuration, cfg.internal.slotMaxDuration, "expected closedSlotMinDuration= %v . But Found %v", expected.closedSlotMaxDuration, cfg.internal.slotMaxDuration) + assert.Equal(t, expectedImpressionBreak, imps, "2darray mismatch") + assert.Equal(t, MaximizeForDuration, cfg.Algorithm()) + }) + } +} + +/* Benchmarking Tests */ +func BenchmarkGetImpressions(b *testing.B) { + for _, impTest := range impressionsTests { + b.Run(impTest.scenario, func(b *testing.B) { + in := testdata.Input[impTest.scenario] + p := newTestPod(int64(in[0]), int64(in[1]), in[2], in[3], in[4], in[5]) + for n := 0; n < b.N; n++ { + cfg := newMaximizeForDuration(p.podMinDuration, p.podMaxDuration, p.vPod) + cfg.Get() + } + }) + } +} + +func newTestPod(podMinDuration, podMaxDuration int64, slotMinDuration, slotMaxDuration, minAds, maxAds int) *TestAdPod { + testPod := TestAdPod{} + + pod := openrtb_ext.VideoAdPod{} + + pod.MinDuration = &slotMinDuration + pod.MaxDuration = &slotMaxDuration + pod.MinAds = &minAds + pod.MaxAds = &maxAds + + testPod.vPod = pod + testPod.podMinDuration = podMinDuration + testPod.podMaxDuration = podMaxDuration + return &testPod +} diff --git a/endpoints/openrtb2/ctv/impressions/min_max_algorithm.go b/endpoints/openrtb2/ctv/impressions/min_max_algorithm.go new file mode 100644 index 00000000000..a6bc043fa5e --- /dev/null +++ b/endpoints/openrtb2/ctv/impressions/min_max_algorithm.go @@ -0,0 +1,191 @@ +package impressions + +import ( + "fmt" + "math" + "strconv" + "strings" + "sync" + + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// keyDelim used as separator in forming key of maxExpectedDurationMap +var keyDelim = "," + +type config struct { + IImpressions + generator []generator + // maxExpectedDurationMap contains key = min , max duration, value = 0 -no of impressions, 1 + // this map avoids the unwanted repeatations of impressions generated + // Example, + // Step 1 : {{2, 17}, {15, 15}, {15, 15}, {10, 10}, {10, 10}, {10, 10}} + // Step 2 : {{2, 17}, {15, 15}, {15, 15}, {10, 10}, {10, 10}, {10, 10}} + // Step 3 : {{25, 25}, {25, 25}, {2, 22}, {5, 5}} + // Step 4 : {{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}} + // Step 5 : {{15, 15}, {15, 15}, {15, 15}, {15, 15}} + // Optimized Output : {{2, 17}, {15, 15},{15, 15},{15, 15},{15, 15},{10, 10},{10, 10},{10, 10},{10, 10},{10, 10},{10, 10},{25, 25}, {25, 25},{2, 22}, {5, 5}} + // This map will contains : {2, 17} = 1, {15, 15} = 4, {10, 10} = 6, {25, 25} = 2, {2, 22} = 1, {5, 5} =1 + maxExpectedDurationMap map[string][2]int + requested pod +} + +// newMinMaxAlgorithm constructs instance of MinMaxAlgorithm +// It computes durations for Ad Slot and Ad Pod in multiple of X +// it also considers minimum configurations present in the request +func newMinMaxAlgorithm(podMinDuration, podMaxDuration int64, p openrtb_ext.VideoAdPod) config { + generator := make([]generator, 0) + // step 1 - same as Algorithm1 + generator = append(generator, initGenerator(podMinDuration, podMaxDuration, p, *p.MinAds, *p.MaxAds)) + // step 2 - pod duration = pod max, no of ads = max ads + generator = append(generator, initGenerator(podMaxDuration, podMaxDuration, p, *p.MaxAds, *p.MaxAds)) + // step 3 - pod duration = pod max, no of ads = min ads + generator = append(generator, initGenerator(podMaxDuration, podMaxDuration, p, *p.MinAds, *p.MinAds)) + // step 4 - pod duration = pod min, no of ads = max ads + generator = append(generator, initGenerator(podMinDuration, podMinDuration, p, *p.MaxAds, *p.MaxAds)) + // step 5 - pod duration = pod min, no of ads = min ads + generator = append(generator, initGenerator(podMinDuration, podMinDuration, p, *p.MinAds, *p.MinAds)) + + return config{generator: generator, requested: generator[0].requested} +} + +func initGenerator(podMinDuration, podMaxDuration int64, p openrtb_ext.VideoAdPod, minAds, maxAds int) generator { + config := newConfigWithMultipleOf(podMinDuration, podMaxDuration, newVideoAdPod(p, minAds, maxAds), multipleOf) + return config +} + +func newVideoAdPod(p openrtb_ext.VideoAdPod, minAds, maxAds int) openrtb_ext.VideoAdPod { + return openrtb_ext.VideoAdPod{MinDuration: p.MinDuration, + MaxDuration: p.MaxDuration, + MinAds: &minAds, + MaxAds: &maxAds} +} + +// Get ... +func (c *config) Get() [][2]int64 { + imps := make([][2]int64, 0) + wg := new(sync.WaitGroup) // ensures each step generating impressions is finished + impsChan := make(chan [][2]int64, len(c.generator)) + for i := 0; i < len(c.generator); i++ { + wg.Add(1) + go get(c.generator[i], impsChan, wg) + } + + // ensure impressions channel is closed + // when all go routines are executed + func() { + defer close(impsChan) + wg.Wait() + }() + + c.maxExpectedDurationMap = make(map[string][2]int, 0) + util.Logf("Step wise breakup ") + for impressions := range impsChan { + for index, impression := range impressions { + impKey := getKey(impression) + setMaximumRepeatations(c, impKey, index+1 == len(impressions)) + } + util.Logf("%v", impressions) + } + + // for impressions array + indexOffset := 0 + for impKey := range c.maxExpectedDurationMap { + totalRepeations := c.getRepeations(impKey) + for repeation := 1; repeation <= totalRepeations; repeation++ { + imps = append(imps, getImpression(impKey)) + } + // if exact pod duration is provided then do not compute + // min duration. Instead expect min duration same as max duration + // It must be set by underneath algorithm + if c.requested.podMinDuration != c.requested.podMaxDuration { + computeMinDuration(*c, imps[:], indexOffset, indexOffset+totalRepeations) + } + indexOffset += totalRepeations + } + return imps +} + +// getImpression constructs the impression object with min and max duration +// from input impression key +func getImpression(key string) [2]int64 { + decodedKey := strings.Split(key, keyDelim) + minDuration, _ := strconv.Atoi(decodedKey[MinDuration]) + maxDuration, _ := strconv.Atoi(decodedKey[MaxDuration]) + return [2]int64{int64(minDuration), int64(maxDuration)} +} + +// setMaximumRepeatations avoids unwanted repeatations of impression object. Using following logic +// maxExpectedDurationMap value contains 2 types of storage +// 1. value[0] - represents current counter where final repeataions are stored +// 2. value[1] - local storage used by each impression object to add more repeatations if required +// +// impKey - key used to obtained already added repeatations for given impression +// updateCurrentCounter - if true and if current local storage value > repeatations then repeations will be +// updated as current counter +func setMaximumRepeatations(c *config, impKey string, updateCurrentCounter bool) { + // update maxCounter of each impression + value := c.maxExpectedDurationMap[impKey] + value[1]++ // increment max counter (contains no of repeatations for given iteration) + c.maxExpectedDurationMap[impKey] = value + // if val(maxCounter) > actual store then consider temporary value as actual value + if updateCurrentCounter { + for k := range c.maxExpectedDurationMap { + val := c.maxExpectedDurationMap[k] + if val[1] > val[0] { + val[0] = val[1] + } + // clear maxCounter + val[1] = 0 + c.maxExpectedDurationMap[k] = val // reassign + } + } + +} + +// getKey returns the key used for refering values of maxExpectedDurationMap +// key is computed based on input impression object having min and max durations +func getKey(impression [2]int64) string { + return fmt.Sprintf("%v%v%v", impression[MinDuration], keyDelim, impression[MaxDuration]) +} + +// getRepeations returns number of repeatations at that time that this algorithm will +// return w.r.t. input impressionKey +func (c config) getRepeations(impressionKey string) int { + return c.maxExpectedDurationMap[impressionKey][0] +} + +// get is internal function that actually computes the number of impressions +// based on configrations present in c +func get(c generator, ch chan [][2]int64, wg *sync.WaitGroup) { + defer wg.Done() + imps := c.Get() + util.Logf("A2 Impressions = %v\n", imps) + ch <- imps +} + +// Algorithm returns MinMaxAlgorithm +func (c config) Algorithm() Algorithm { + return MinMaxAlgorithm +} + +func computeMinDuration(c config, impressions [][2]int64, start int, end int) { + r := c.requested + // 5/2 => q = 2 , r = 1 => 2.5 => 3 + minDuration := int64(math.Round(float64(r.podMinDuration) / float64(r.minAds))) + for i := start; i < end; i++ { + impression := &impressions[i] + // ensure imp duration boundaries + // if boundaries are not honoured keep min duration which is computed as is + if minDuration >= r.slotMinDuration && minDuration <= impression[MaxDuration] { + // override previous value + impression[MinDuration] = minDuration + } else { + // boundaries are not matching keep min value as is + util.Logf("False : minDuration (%v) >= r.slotMinDuration (%v) && minDuration (%v) <= impression[MaxDuration] (%v)", minDuration, r.slotMinDuration, minDuration, impression[MaxDuration]) + util.Logf("Hence, setting request level slot minduration (%v) ", r.slotMinDuration) + impression[MinDuration] = r.slotMinDuration + } + } +} diff --git a/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go b/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go new file mode 100644 index 00000000000..f6d5f234a9b --- /dev/null +++ b/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go @@ -0,0 +1,601 @@ +package impressions + +import ( + "sort" + "testing" + + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/impressions/testdata" + "github.com/stretchr/testify/assert" +) + +type expectedOutputA2 struct { + step1 [][2]int64 // input passed as is + step2 [][2]int64 // pod duration = pod max duration, no of ads = maxads + step3 [][2]int64 // pod duration = pod max duration, no of ads = minads + step4 [][2]int64 // pod duration = pod min duration, no of ads = maxads + step5 [][2]int64 // pod duration = pod min duration, no of ads = minads +} + +var impressionsTestsA2 = []struct { + scenario string // Testcase scenario + //in []int // Testcase input + out expectedOutputA2 // Testcase execpted output +}{ + {scenario: "TC2", out: expectedOutputA2{ + step1: [][2]int64{{15, 15}, {15, 15}, {15, 15}, {15, 15}, {15, 15}, {15, 15}}, + step2: [][2]int64{{11, 13}, {11, 11}, {11, 11}, {11, 11}, {11, 11}, {11, 11}, {11, 11}, {11, 11}}, + step3: [][2]int64{}, // 90 90 15 15 2 2 + step4: [][2]int64{}, // 1,1, 15,15, 8 8 + step5: [][2]int64{}, // 1,1, 15,15, 2 2 + }}, + {scenario: "TC3", out: expectedOutputA2{ + step1: [][2]int64{{15, 15}, {15, 15}, {15, 15}, {15, 15}}, + step2: [][2]int64{}, // 90 90 15 15 4 4 + step3: [][2]int64{}, // 90 90 15 15 2 2 + step4: [][2]int64{}, // 1 1 15 15 4 4 + step5: [][2]int64{}, // 1 1 15 15 2 2 + }}, + {scenario: "TC4", out: expectedOutputA2{ + step1: [][2]int64{{15, 15}}, + step2: [][2]int64{{15, 15}}, // 15 15 5 15 1 1 + step3: [][2]int64{{15, 15}}, // 15 15 5 15 1 1 + step4: [][2]int64{{1, 1}}, // 1 1 5 15 1 1 + step5: [][2]int64{{1, 1}}, // 1 1 5 15 1 1 + }}, + {scenario: "TC5", out: expectedOutputA2{ + step1: [][2]int64{{10, 10}, {5, 5}}, + step2: [][2]int64{{10, 10}, {5, 5}}, // 15, 15, 5, 15, 2, 2 + step3: [][2]int64{{15, 15}}, // 15, 15, 5, 15, 1, 1 + step4: [][2]int64{}, // 1, 1, 5, 15, 2, 2 + step5: [][2]int64{{1, 1}}, // 1, 1, 5, 15, 1, 1 + }}, + {scenario: "TC6", out: expectedOutputA2{ + // 5, 90, 5, 15, 1, 8 + step1: [][2]int64{{15, 15}, {15, 15}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + // 90, 90, 5, 15, 8, 8 + step2: [][2]int64{{15, 15}, {15, 15}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + // 90, 90, 5, 15, 1, 1 + step3: [][2]int64{}, + // 1, 1, 5, 15, 8, 8 + step4: [][2]int64{}, + // 1, 1, 5, 15, 1, 1 + step5: [][2]int64{{1, 1}}, + }}, + {scenario: "TC7", out: expectedOutputA2{ + // 15, 30, 10, 15, 1, 1 + step1: [][2]int64{{15, 15}}, + // 30, 30, 10, 15, 1, 1 + step2: [][2]int64{}, + // 30, 30, 10, 15, 1, 1 + step3: [][2]int64{}, + // 15, 15, 10, 15, 1, 1 + step4: [][2]int64{{15, 15}}, + // 15, 15, 10, 15, 1, 1 + step5: [][2]int64{{15, 15}}, + }}, + {scenario: "TC8", out: expectedOutputA2{ + // 35, 35, 10, 35, 3, 40 + step1: [][2]int64{{15, 15}, {10, 10}, {10, 10}}, + // 35, 35, 10, 35, 40, 40 + step2: [][2]int64{}, + // 35, 35, 10, 35, 3, 3 + step3: [][2]int64{{15, 15}, {10, 10}, {10, 10}}, + // 35, 35, 10, 35, 40, 40 + step4: [][2]int64{}, + // 35, 35, 10, 35, 3, 3 + step5: [][2]int64{{15, 15}, {10, 10}, {10, 10}}, + }}, + {scenario: "TC9", out: expectedOutputA2{ + // 35, 35, 10, 35, 6, 40 + step1: [][2]int64{}, + // 35, 35, 10, 35, 40, 40 + step2: [][2]int64{}, + // 35, 35, 10, 35, 6, 6 + step3: [][2]int64{}, + // 35, 35, 10, 35, 40, 40 + step4: [][2]int64{}, + // 35, 35, 10, 35, 6, 6 + step5: [][2]int64{}, + }}, + {scenario: "TC10", out: expectedOutputA2{ + // 35, 65, 10, 35, 6, 40 + step1: [][2]int64{{15, 15}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + // 65, 65, 10, 35, 40, 40 + step2: [][2]int64{}, + // 65, 65, 10, 35, 6, 6 + step3: [][2]int64{{15, 15}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + // 35, 35, 10, 35, 40, 40 + step4: [][2]int64{}, + // 35, 35, 10, 35, 6, 6 + step5: [][2]int64{}, + }}, + {scenario: "TC11", out: expectedOutputA2{ + // 35, 65, 10, 35, 7, 40 + step1: [][2]int64{{9, 11}, {9, 9}, {9, 9}, {9, 9}, {9, 9}, {9, 9}, {9, 9}}, + // 65, 65, 10, 35, 40, 40 + step2: [][2]int64{}, + // 65, 65, 10, 35, 7, 7 + step3: [][2]int64{{9, 11}, {9, 9}, {9, 9}, {9, 9}, {9, 9}, {9, 9}, {9, 9}}, + // 35, 35, 10, 35, 40, 40 + step4: [][2]int64{}, + // 35, 35, 10, 35, 7, 7 + step5: [][2]int64{}, + }}, + {scenario: "TC12", out: expectedOutputA2{ + step1: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + step2: [][2]int64{}, + step3: [][2]int64{{20, 20}, {20, 20}, {15, 15}, {15, 15}, {15, 15}, {15, 15}}, + step4: [][2]int64{}, + step5: [][2]int64{{20, 20}, {20, 20}, {15, 15}, {15, 15}, {15, 15}, {15, 15}}, + }}, + {scenario: "TC13", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC14", out: expectedOutputA2{ + step1: [][2]int64{{5, 9}, {5, 9}, {5, 9}, {5, 9}, {5, 9}, {5, 9}}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{{5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}}, + step5: [][2]int64{}, + }}, + {scenario: "TC15", out: expectedOutputA2{ + step1: [][2]int64{{5, 9}, {5, 9}, {5, 9}, {5, 9}, {5, 9}}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{{5, 9}, {5, 6}, {5, 5}, {5, 5}, {5, 5}}, + step5: [][2]int64{}, + }}, + {scenario: "TC27", out: expectedOutputA2{ + step1: [][2]int64{{30, 30}, {30, 30}, {30, 30}}, + step2: [][2]int64{{30, 30}, {30, 30}, {30, 30}}, + step3: [][2]int64{{45, 45}, {45, 45}}, + step4: [][2]int64{}, + step5: [][2]int64{{2, 3}, {2, 2}}, + }}, + {scenario: "TC16", out: expectedOutputA2{ + step1: [][2]int64{{1, 12}, {1, 12}, {1, 12}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + step2: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {1, 6}}, + step3: [][2]int64{}, + step4: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {1, 6}}, + step5: [][2]int64{}, + }}, + {scenario: "TC17", out: expectedOutputA2{ + step1: [][2]int64{{1, 12}, {1, 12}, {1, 12}, {1, 12}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + step2: [][2]int64{{1, 11}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {6, 7}}, + step3: [][2]int64{}, + step4: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {6, 7}}, + step5: [][2]int64{}, + }}, + {scenario: "TC18", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC19", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC20", out: expectedOutputA2{ + step1: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC21", out: expectedOutputA2{ + step1: [][2]int64{{3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC23", out: expectedOutputA2{ + step1: [][2]int64{{4, 14}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{{4, 13}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}}, + step5: [][2]int64{}, + }}, + {scenario: "TC24", out: expectedOutputA2{ + step1: [][2]int64{{60, 69}, {65, 65}}, + step2: [][2]int64{}, + step3: [][2]int64{{60, 69}, {65, 65}}, + step4: [][2]int64{}, + step5: [][2]int64{{60, 69}, {65, 65}}, + }}, + {scenario: "TC25", out: expectedOutputA2{ + step1: [][2]int64{{1, 68}, {20, 20}}, + step2: [][2]int64{{1, 68}, {20, 20}}, + step3: [][2]int64{{1, 68}, {20, 20}}, + step4: [][2]int64{{1, 68}, {20, 20}}, + step5: [][2]int64{{1, 68}, {20, 20}}, + }}, + {scenario: "TC26", out: expectedOutputA2{ + step1: [][2]int64{{45, 45}, {45, 45}}, + step2: [][2]int64{}, + step3: [][2]int64{{45, 45}, {45, 45}}, + step4: [][2]int64{}, + step5: [][2]int64{{45, 45}, {45, 45}}, + }}, + {scenario: "TC27", out: expectedOutputA2{ + step1: [][2]int64{{30, 30}, {30, 30}, {30, 30}}, + step2: [][2]int64{{30, 30}, {30, 30}, {30, 30}}, + step3: [][2]int64{{45, 45}, {45, 45}}, + step4: [][2]int64{}, + step5: [][2]int64{{2, 3}, {2, 2}}, + }}, + {scenario: "TC28", out: expectedOutputA2{ + step1: [][2]int64{{30, 30}, {30, 30}, {30, 30}, {30, 30}, {30, 30}, {30, 30}}, + step2: [][2]int64{{30, 30}, {30, 30}, {30, 30}, {30, 30}, {30, 30}, {30, 30}}, + step3: [][2]int64{{90, 90}, {90, 90}}, + step4: [][2]int64{}, + step5: [][2]int64{{2, 3}, {2, 2}}, + }}, + {scenario: "TC29", out: expectedOutputA2{ + step1: [][2]int64{{25, 25}, {20, 20}, {20, 20}}, + step2: [][2]int64{{25, 25}, {20, 20}, {20, 20}}, + step3: [][2]int64{{35, 35}, {30, 30}}, + step4: [][2]int64{}, + step5: [][2]int64{{2, 3}, {2, 2}}, + }}, + {scenario: "TC30", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC31", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC32", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC33", out: expectedOutputA2{ + step1: [][2]int64{{30, 42}, {35, 35}, {35, 35}, {35, 35}}, + step2: [][2]int64{}, + step3: [][2]int64{{30, 42}, {35, 35}, {35, 35}, {35, 35}}, + step4: [][2]int64{}, + step5: [][2]int64{{30, 42}, {35, 35}, {35, 35}, {35, 35}}, + }}, + {scenario: "TC34", out: expectedOutputA2{ + step1: [][2]int64{{30, 30}, {30, 30}, {30, 30}}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC35", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC36", out: expectedOutputA2{ + step1: [][2]int64{{45, 45}, {45, 45}}, + step2: [][2]int64{}, + step3: [][2]int64{{45, 45}, {45, 45}}, + step4: [][2]int64{}, + step5: [][2]int64{{45, 45}, {45, 45}}, + }}, + {scenario: "TC37", out: expectedOutputA2{ + step1: [][2]int64{{25, 25}, {20, 20}}, + step2: [][2]int64{}, + step3: [][2]int64{{25, 25}, {20, 20}}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC38", out: expectedOutputA2{ + step1: [][2]int64{{25, 25}, {25, 25}, {20, 20}, {20, 20}}, + step2: [][2]int64{}, + step3: [][2]int64{{45, 45}, {45, 45}}, + step4: [][2]int64{}, + step5: [][2]int64{{45, 45}, {45, 45}}, + }}, + {scenario: "TC39", out: expectedOutputA2{ + step1: [][2]int64{{25, 25}, {25, 25}, {20, 20}, {20, 20}}, + step2: [][2]int64{}, + step3: [][2]int64{{45, 45}, {45, 45}}, + step4: [][2]int64{}, + step5: [][2]int64{{30, 30}, {30, 30}}, + }}, + {scenario: "TC40", out: expectedOutputA2{ + step1: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {5, 5}}, + step2: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {5, 5}}, + step3: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {5, 5}}, + step4: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {5, 5}}, + step5: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {5, 5}}, + }}, + {scenario: "TC41", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}}, + step5: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}}, + }}, + {scenario: "TC42", out: expectedOutputA2{ + step1: [][2]int64{{1, 1}}, + step2: [][2]int64{{1, 1}}, + step3: [][2]int64{{1, 1}}, + step4: [][2]int64{{1, 1}}, + step5: [][2]int64{{1, 1}}, + }}, + {scenario: "TC43", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC44", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC45", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC46", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC47", out: expectedOutputA2{ + step1: [][2]int64{{6, 6}}, + step2: [][2]int64{{6, 6}}, + step3: [][2]int64{{6, 6}}, + step4: [][2]int64{{6, 6}}, + step5: [][2]int64{{6, 6}}, + }}, + {scenario: "TC48", out: expectedOutputA2{ + step1: [][2]int64{{6, 6}, {6, 6}}, + step2: [][2]int64{{6, 6}, {6, 6}}, + step3: [][2]int64{}, + step4: [][2]int64{{6, 6}, {6, 6}}, + step5: [][2]int64{}, + }}, + {scenario: "TC49", out: expectedOutputA2{ + step1: [][2]int64{}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC50", out: expectedOutputA2{ + step1: [][2]int64{{1, 1}}, + step2: [][2]int64{{1, 1}}, + step3: [][2]int64{{1, 1}}, + step4: [][2]int64{{1, 1}}, + step5: [][2]int64{{1, 1}}, + }}, + {scenario: "TC51", out: expectedOutputA2{ + step1: [][2]int64{{13, 13}, {13, 13}, {13, 13}}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC52", out: expectedOutputA2{ + step1: [][2]int64{{12, 18}, {12, 18}, {12, 18}, {12, 18}}, + step2: [][2]int64{{12, 18}, {12, 18}, {12, 18}, {12, 18}}, + step3: [][2]int64{}, + step4: [][2]int64{{12, 18}, {12, 18}, {12, 17}, {15, 15}}, + step5: [][2]int64{}, + }}, + {scenario: "TC53", out: expectedOutputA2{ + step1: [][2]int64{{20, 20}, {20, 20}, {20, 20}, {20, 20}, {20, 20}, {20, 20}, {1, 6}}, + step2: [][2]int64{{20, 20}, {20, 20}, {20, 20}, {20, 20}, {20, 20}, {20, 20}, {1, 6}}, + step3: [][2]int64{}, + step4: [][2]int64{{20, 20}, {20, 20}, {20, 20}, {20, 20}, {20, 20}, {20, 20}, {1, 6}}, + step5: [][2]int64{}, + }}, + // {1, 74, 12, 12, 1, 6} + {scenario: "TC55", out: expectedOutputA2{ + step1: [][2]int64{{12, 12}, {12, 12}, {12, 12}, {12, 12}, {12, 12}, {12, 12}}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{}, + step5: [][2]int64{}, + }}, + {scenario: "TC56", out: expectedOutputA2{ + step1: [][2]int64{{126, 126}}, + step2: [][2]int64{{126, 126}}, + step3: [][2]int64{{126, 126}}, + step4: [][2]int64{{126, 126}}, + step5: [][2]int64{{126, 126}}, + }}, + {scenario: "TC57", out: expectedOutputA2{ + step1: [][2]int64{{126, 126}}, + step2: [][2]int64{}, + step3: [][2]int64{{126, 126}}, + step4: [][2]int64{}, + step5: [][2]int64{{126, 126}}, + }}, + {scenario: "TC58", out: expectedOutputA2{ + step1: [][2]int64{{25, 25}, {25, 25}, {20, 20}, {20, 20}}, + step2: [][2]int64{{25, 25}, {25, 25}, {20, 20}, {20, 20}}, + step3: [][2]int64{{45, 45}, {45, 45}}, + step4: [][2]int64{}, + step5: [][2]int64{{15, 15}, {15, 15}}, + }}, + {scenario: "TC59", out: expectedOutputA2{ + step1: [][2]int64{{45, 45}}, + step2: [][2]int64{}, + step3: [][2]int64{}, + step4: [][2]int64{{30, 30}}, + step5: [][2]int64{{30, 30}}, + }}, + // {scenario: "TC1" , out: expectedOutputA2{ + // step1: [][2]int64{}, + // step2: [][2]int64{}, + // step3: [][2]int64{}, + // step4: [][2]int64{}, + // step5: [][2]int64{}, + // }}, + // Testcases with realistic scenarios + + // {scenario: "TC_3_to_4_Ads_Of_5_to_10_Sec" /*in: []int{15, 40, 5, 10, 3, 4},*/, out: expectedOutputA2{ + // // 15, 40, 5, 10, 3, 4 + // step1: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}}, + // // 40, 40, 5, 10, 4, 4 + // step2: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}}, + // // 40, 40, 5, 10, 3, 3 + // step3: [][2]int64{}, + // // 15, 15, 5, 10, 4, 4 + // step4: [][2]int64{}, + // // 15, 15, 5, 10, 3, 3 + // step5: [][2]int64{{5, 5}, {5, 5}, {5, 5}}, + // }}, + // {scenario: "TC_4_to_6_Ads_Of_2_to_25_Sec" /*in: []int{60, 77, 2, 25, 4, 6}, */, out: expectedOutputA2{ + // // 60, 77, 2, 25, 4, 6 + // step1: [][2]int64{{2, 17}, {15, 15}, {15, 15}, {10, 10}, {10, 10}, {10, 10}}, + // // 77, 77, 5, 25, 6, 6 + // step2: [][2]int64{{2, 17}, {15, 15}, {15, 15}, {10, 10}, {10, 10}, {10, 10}}, + // // 77, 77, 5, 25, 4, 4 + // step3: [][2]int64{{25, 25}, {25, 25}, {2, 22}, {5, 5}}, + // // 60, 60, 5, 25, 6, 6 + // step4: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + // // 60, 60, 5, 25, 4, 4 + // step5: [][2]int64{{15, 15}, {15, 15}, {15, 15}, {15, 15}}, + // }}, + + // {scenario: "TC_2_to_6_ads_of_15_to_45_sec" /*in: []int{60, 90, 15, 45, 2, 6},*/, out: expectedOutputA2{ + // // 60, 90, 15, 45, 2, 6 + // step1: [][2]int64{{15, 15}, {15, 15}, {15, 15}, {15, 15}, {15, 15}, {15, 15}}, + // // 90, 90, 15, 45, 6, 6 + // step2: [][2]int64{{15, 15}, {15, 15}, {15, 15}, {15, 15}, {15, 15}, {15, 15}}, + // // 90, 90, 15, 45, 2, 2 + // step3: [][2]int64{{45, 45}, {45, 45}}, + // // 60, 60, 15, 45, 6, 6 + // step4: [][2]int64{}, + // // 60, 60, 15, 45, 2, 2 + // step5: [][2]int64{{30, 30}, {30, 30}}, + // }}, + +} + +func TestGetImpressionsA2(t *testing.T) { + for _, impTest := range impressionsTestsA2 { + t.Run(impTest.scenario, func(t *testing.T) { + in := testdata.Input[impTest.scenario] + p := newTestPod(int64(in[0]), int64(in[1]), in[2], in[3], in[4], in[5]) + a2 := newMinMaxAlgorithm(p.podMinDuration, p.podMaxDuration, p.vPod) + expectedMergedOutput := make([][2]int64, 0) + // explictly looping in order to check result of individual generator + for step, gen := range a2.generator { + switch step { + case 0: // algo1 equaivalent + assert.Equal(t, impTest.out.step1, gen.Get()) + expectedMergedOutput = appendOptimized(expectedMergedOutput, impTest.out.step1) + break + case 1: // pod duration = pod max duration, no of ads = maxads + assert.Equal(t, impTest.out.step2, gen.Get()) + expectedMergedOutput = appendOptimized(expectedMergedOutput, impTest.out.step2) + break + case 2: // pod duration = pod max duration, no of ads = minads + assert.Equal(t, impTest.out.step3, gen.Get()) + expectedMergedOutput = appendOptimized(expectedMergedOutput, impTest.out.step3) + break + case 3: // pod duration = pod min duration, no of ads = maxads + assert.Equal(t, impTest.out.step4, gen.Get()) + expectedMergedOutput = appendOptimized(expectedMergedOutput, impTest.out.step4) + break + case 4: // pod duration = pod min duration, no of ads = minads + assert.Equal(t, impTest.out.step5, gen.Get()) + expectedMergedOutput = appendOptimized(expectedMergedOutput, impTest.out.step5) + break + } + + } + // also verify merged output + expectedMergedOutput = testdata.Scenario[impTest.scenario].MinMaxAlgorithm + out := sortOutput(a2.Get()) + //fmt.Println(out) + assert.Equal(t, sortOutput(expectedMergedOutput), out) + }) + } +} + +func BenchmarkGetImpressionsA2(b *testing.B) { + for _, impTest := range impressionsTestsA2 { + for i := 0; i < b.N; i++ { + in := testdata.Input[impTest.scenario] + p := newTestPod(int64(in[0]), int64(in[1]), in[2], in[3], in[4], in[5]) + a2 := newMinMaxAlgorithm(p.podMinDuration, p.podMaxDuration, p.vPod) + a2.Get() + } + } +} + +func sortOutput(imps [][2]int64) [][2]int64 { + sort.Slice(imps, func(i, j int) bool { + return imps[i][1] < imps[j][1] + }) + return imps +} + +func appendOptimized(slice [][2]int64, elems [][2]int64) [][2]int64 { + m := make(map[string]int, 0) + keys := make([]string, 0) + for _, sel := range slice { + k := getKey(sel) + m[k]++ + keys = append(keys, k) + } + elemsmap := make(map[string]int, 0) + for _, ele := range elems { + elemsmap[getKey(ele)]++ + } + + for k := range elemsmap { + if elemsmap[k] > m[k] { + m[k] = elemsmap[k] + } + + keyPresent := false + for _, kl := range keys { + if kl == k { + keyPresent = true + break + } + } + + if !keyPresent { + keys = append(keys, k) + } + } + + optimized := make([][2]int64, 0) + for k, v := range m { + for i := 1; i <= v; i++ { + optimized = append(optimized, getImpression(k)) + } + } + return optimized +} diff --git a/endpoints/openrtb2/ctv/impressions/testdata/input.go b/endpoints/openrtb2/ctv/impressions/testdata/input.go new file mode 100644 index 00000000000..3ee64544b95 --- /dev/null +++ b/endpoints/openrtb2/ctv/impressions/testdata/input.go @@ -0,0 +1,61 @@ +package testdata + +// Input Test Input +var Input = map[string][]int{ + "TC2": {1, 90, 11, 15, 2, 8}, + "TC3": {1, 90, 11, 15, 2, 4}, + "TC4": {1, 15, 1, 15, 1, 1}, + "TC5": {1, 15, 1, 15, 1, 2}, + "TC6": {1, 90, 1, 15, 1, 8}, + "TC7": {15, 30, 8, 15, 1, 1}, + "TC8": {35, 35, 10, 35, 3, 40}, + "TC9": {35, 35, 10, 35, 6, 40}, + "TC10": {35, 65, 10, 35, 6, 40}, + "TC11": {35, 65, 9, 35, 7, 40}, + "TC12": {100, 100, 10, 35, 6, 40}, + "TC13": {60, 60, 5, 9, 1, 6}, + "TC14": {30, 60, 5, 9, 1, 6}, + "TC15": {30, 60, 5, 9, 1, 5}, + "TC16": {126, 126, 1, 12, 7, 13}, /* Exact Pod Duration */ + "TC17": {127, 128, 1, 12, 7, 13}, + "TC18": {125, 125, 4, 4, 1, 1}, + "TC19": {90, 90, 7, 9, 3, 5}, + "TC20": {90, 90, 5, 10, 1, 11}, + "TC21": {2, 170, 3, 9, 4, 9}, + "TC23": {118, 124, 4, 17, 6, 15}, + "TC24": {134, 134, 60, 90, 2, 3}, + "TC25": {88, 88, 1, 80, 2, 2}, + "TC26": {90, 90, 45, 45, 2, 3}, + "TC27": {5, 90, 2, 45, 2, 3}, + "TC28": {5, 180, 2, 90, 2, 6}, + "TC29": {5, 65, 2, 35, 2, 3}, + "TC30": {123, 123, 34, 34, 3, 3}, + "TC31": {123, 123, 31, 31, 3, 3}, + "TC32": {134, 134, 63, 63, 2, 3}, + "TC33": {147, 147, 30, 60, 4, 6}, + "TC34": {88, 102, 30, 30, 3, 3}, + "TC35": {88, 102, 30, 42, 3, 3}, + "TC36": {90, 90, 45, 45, 2, 5}, + "TC37": {10, 45, 20, 45, 2, 5}, + "TC38": {90, 90, 20, 45, 2, 5}, + "TC39": {60, 90, 20, 45, 2, 5}, + "TC40": {95, 95, 5, 45, 10, 10}, + "TC41": {95, 123, 5, 45, 13, 13}, + "TC42": {1, 1, 1, 1, 1, 1}, + "TC43": {2, 2, 2, 2, 2, 2}, + "TC44": {0, 0, 0, 0, 0, 0}, + "TC45": {-1, -2, -3, -4, -5, -6}, + "TC46": {-1, -1, -1, -1, -1, -1}, + "TC47": {6, 6, 6, 6, 1, 1}, + "TC48": {12, 12, 6, 6, 1, 2}, + "TC49": {12, 12, 7, 7, 1, 2}, + "TC50": {1, 1, 1, 1, 1, 1}, + "TC51": {31, 43, 11, 13, 2, 3}, + "TC52": {68, 72, 12, 18, 2, 4}, + "TC53": {126, 126, 1, 20, 1, 7}, + "TC55": {1, 74, 12, 12, 1, 6}, + "TC56": {126, 126, 126, 126, 1, 1}, + "TC57": {126, 126, 126, 126, 1, 3}, + "TC58": {30, 90, 15, 45, 2, 4}, + "TC59": {30, 90, 15, 45, 1, 1}, +} diff --git a/endpoints/openrtb2/ctv/impressions/testdata/output.go b/endpoints/openrtb2/ctv/impressions/testdata/output.go new file mode 100644 index 00000000000..d7e854fc575 --- /dev/null +++ b/endpoints/openrtb2/ctv/impressions/testdata/output.go @@ -0,0 +1,236 @@ +package testdata + +type eout struct { + MaximizeForDuration [][2]int64 + MinMaxAlgorithm [][2]int64 +} + +// Scenario returns expected impression breaks for given algorithm and for given +// test scenario +var Scenario = map[string]eout{ + + "TC2": { + MaximizeForDuration: [][2]int64{{15, 15}, {15, 15}, {15, 15}, {15, 15}, {15, 15}, {15, 15}}, + MinMaxAlgorithm: [][2]int64{{11, 13}, {11, 11}, {11, 11}, {11, 11}, {11, 11}, {11, 11}, {11, 11}, {11, 11}, {11, 15}, {11, 15}, {11, 15}, {11, 15}, {11, 15}, {11, 15}}}, + + "TC3": { + MaximizeForDuration: [][2]int64{{15, 15}, {15, 15}, {15, 15}, {15, 15}}, + MinMaxAlgorithm: [][2]int64{{11, 15}, {11, 15}, {11, 15}, {11, 15}}, + }, + "TC4": { + MaximizeForDuration: [][2]int64{{15, 15}}, + MinMaxAlgorithm: [][2]int64{{1, 15}, {1, 1}}, + }, + "TC5": { + MaximizeForDuration: [][2]int64{{10, 10}, {5, 5}}, + MinMaxAlgorithm: [][2]int64{{1, 1}, {1, 5}, {1, 15}, {1, 10}}, + }, + "TC6": { + MaximizeForDuration: [][2]int64{{15, 15}, {15, 15}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + MinMaxAlgorithm: [][2]int64{{1, 15}, {1, 15}, {1, 10}, {1, 10}, {1, 10}, {1, 10}, {1, 10}, {1, 10}, {1, 1}}, + }, + "TC7": { + MaximizeForDuration: [][2]int64{{15, 15}}, + MinMaxAlgorithm: [][2]int64{{15, 15}}, + }, + "TC8": { + MaximizeForDuration: [][2]int64{{15, 15}, {10, 10}, {10, 10}}, + MinMaxAlgorithm: [][2]int64{{10, 10}, {10, 10}, {15, 15}}, + }, + "TC9": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC10": { + MaximizeForDuration: [][2]int64{{15, 15}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + MinMaxAlgorithm: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 15}}, + }, + "TC11": { + MaximizeForDuration: [][2]int64{{9, 11}, {9, 9}, {9, 9}, {9, 9}, {9, 9}, {9, 9}, {9, 9}}, + MinMaxAlgorithm: [][2]int64{{9, 11}, {9, 9}, {9, 9}, {9, 9}, {9, 9}, {9, 9}, {9, 9}}, + }, + "TC12": { + MaximizeForDuration: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + MinMaxAlgorithm: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {20, 20}, {20, 20}, {15, 15}, {15, 15}, {15, 15}, {15, 15}}, + }, + "TC13": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC14": { + MaximizeForDuration: [][2]int64{{5, 9}, {5, 9}, {5, 9}, {5, 9}, {5, 9}, {5, 9}}, + MinMaxAlgorithm: [][2]int64{{5, 9}, {5, 9}, {5, 9}, {5, 9}, {5, 9}, {5, 9}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}}, + }, + "TC15": { + MaximizeForDuration: [][2]int64{{5, 9}, {5, 9}, {5, 9}, {5, 9}, {5, 9}}, + MinMaxAlgorithm: [][2]int64{{5, 9}, {5, 9}, {5, 9}, {5, 9}, {5, 9}, {5, 6}, {5, 5}, {5, 5}, {5, 5}}, + }, + "TC16": { + MaximizeForDuration: [][2]int64{{1, 12}, {1, 12}, {1, 12}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + MinMaxAlgorithm: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {1, 6}, {1, 12}, {1, 12}, {1, 12}}, + }, + "TC17": { + MaximizeForDuration: [][2]int64{{1, 12}, {1, 12}, {1, 12}, {1, 12}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + MinMaxAlgorithm: [][2]int64{{1, 11}, {1, 7}, {1, 12}, {1, 12}, {1, 12}, {1, 12}, {1, 10}, {1, 10}, {1, 10}, {1, 10}, {1, 10}, {1, 10}, {1, 10}, {1, 10}, {1, 10}, {1, 10}, {1, 10}, {1, 10}}, + }, + "TC18": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC19": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC20": { + MaximizeForDuration: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + MinMaxAlgorithm: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + }, + "TC21": { + MaximizeForDuration: [][2]int64{{3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}}, + MinMaxAlgorithm: [][2]int64{{3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}, {3, 9}}, + }, + "TC23": { + MaximizeForDuration: [][2]int64{{4, 14}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}}, + MinMaxAlgorithm: [][2]int64{{4, 13}, {4, 5}, {4, 5}, {4, 5}, {4, 5}, {4, 5}, {4, 5}, {4, 5}, {4, 14}, {4, 10}, {4, 10}, {4, 10}, {4, 10}, {4, 10}, {4, 10}, {4, 10}, {4, 10}, {4, 10}, {4, 10}, {4, 10}}, + }, + "TC24": { + MaximizeForDuration: [][2]int64{{60, 69}, {65, 65}}, + MinMaxAlgorithm: [][2]int64{{60, 69}, {65, 65}}, + }, + "TC25": { + MaximizeForDuration: [][2]int64{{1, 68}, {20, 20}}, + MinMaxAlgorithm: [][2]int64{{1, 68}, {20, 20}}, + }, + "TC26": { + MaximizeForDuration: [][2]int64{{45, 45}, {45, 45}}, + MinMaxAlgorithm: [][2]int64{{45, 45}, {45, 45}}, + }, + "TC27": { + MaximizeForDuration: [][2]int64{{30, 30}, {30, 30}, {30, 30}}, + MinMaxAlgorithm: [][2]int64{{3, 3}, {2, 2}, {3, 30}, {3, 30}, {3, 30}, {3, 45}, {3, 45}}, + }, + "TC28": { + MaximizeForDuration: [][2]int64{{30, 30}, {30, 30}, {30, 30}, {30, 30}, {30, 30}, {30, 30}}, + MinMaxAlgorithm: [][2]int64{{3, 90}, {3, 90}, {3, 3}, {2, 2}, {3, 30}, {3, 30}, {3, 30}, {3, 30}, {3, 30}, {3, 30}}, + }, + "TC29": { + MaximizeForDuration: [][2]int64{{25, 25}, {20, 20}, {20, 20}}, + MinMaxAlgorithm: [][2]int64{{3, 25}, {3, 20}, {3, 20}, {3, 3}, {2, 2}, {3, 35}, {3, 30}}, + }, + "TC30": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC31": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC32": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC33": { + MaximizeForDuration: [][2]int64{{30, 42}, {35, 35}, {35, 35}, {35, 35}}, + MinMaxAlgorithm: [][2]int64{{30, 42}, {35, 35}, {35, 35}, {35, 35}}, + }, + "TC34": { + MaximizeForDuration: [][2]int64{{30, 30}, {30, 30}, {30, 30}}, + MinMaxAlgorithm: [][2]int64{{30, 30}, {30, 30}, {30, 30}}, + }, + "TC35": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC36": { + MaximizeForDuration: [][2]int64{{45, 45}, {45, 45}}, + MinMaxAlgorithm: [][2]int64{{45, 45}, {45, 45}}, + }, + "TC37": { + MaximizeForDuration: [][2]int64{{25, 25}, {20, 20}}, + MinMaxAlgorithm: [][2]int64{{20, 20}, {20, 25}}, + }, + "TC38": { + MaximizeForDuration: [][2]int64{{25, 25}, {25, 25}, {20, 20}, {20, 20}}, + MinMaxAlgorithm: [][2]int64{{25, 25}, {25, 25}, {20, 20}, {20, 20}, {45, 45}, {45, 45}}, + }, + "TC39": { + MaximizeForDuration: [][2]int64{{25, 25}, {25, 25}, {20, 20}, {20, 20}}, + MinMaxAlgorithm: [][2]int64{{30, 45}, {30, 45}, {30, 30}, {30, 30}, {20, 25}, {20, 25}, {20, 20}, {20, 20}}, + }, + "TC40": { + MaximizeForDuration: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {5, 5}}, + MinMaxAlgorithm: [][2]int64{{10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {5, 5}}, + }, + "TC41": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{{7, 10}, {7, 10}, {7, 10}, {7, 10}, {7, 10}, {7, 10}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}}, + }, + "TC42": { + MaximizeForDuration: [][2]int64{{1, 1}}, + MinMaxAlgorithm: [][2]int64{{1, 1}}, + }, + "TC43": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC44": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC45": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC46": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC47": { + MaximizeForDuration: [][2]int64{{6, 6}}, + MinMaxAlgorithm: [][2]int64{{6, 6}}, + }, + "TC48": { + MaximizeForDuration: [][2]int64{{6, 6}, {6, 6}}, + MinMaxAlgorithm: [][2]int64{{6, 6}, {6, 6}}, + }, + "TC49": { + MaximizeForDuration: [][2]int64{}, + MinMaxAlgorithm: [][2]int64{}, + }, + "TC50": { + MaximizeForDuration: [][2]int64{{1, 1}}, + MinMaxAlgorithm: [][2]int64{{1, 1}}, + }, + "TC51": { + MaximizeForDuration: [][2]int64{{13, 13}, {13, 13}, {13, 13}}, + MinMaxAlgorithm: [][2]int64{{11, 13}, {11, 13}, {11, 13}}, + }, + "TC52": { + MaximizeForDuration: [][2]int64{{12, 18}, {12, 18}, {12, 18}, {12, 18}}, + MinMaxAlgorithm: [][2]int64{{12, 17}, {12, 15}, {12, 18}, {12, 18}, {12, 18}, {12, 18}}, + }, + "TC53": { + MaximizeForDuration: [][2]int64{{20, 20}, {20, 20}, {20, 20}, {20, 20}, {20, 20}, {20, 20}, {1, 6}}, + MinMaxAlgorithm: [][2]int64{{1, 6}, {20, 20}, {20, 20}, {20, 20}, {20, 20}, {20, 20}, {20, 20}}, + }, + "TC55": { + MaximizeForDuration: [][2]int64{{12, 12}, {12, 12}, {12, 12}, {12, 12}, {12, 12}, {12, 12}}, + MinMaxAlgorithm: [][2]int64{{12, 12}, {12, 12}, {12, 12}, {12, 12}, {12, 12}, {12, 12}}, + }, + "TC56": { + MaximizeForDuration: [][2]int64{{126, 126}}, + MinMaxAlgorithm: [][2]int64{{126, 126}}, + }, + "TC57": { + MaximizeForDuration: [][2]int64{{126, 126}}, + MinMaxAlgorithm: [][2]int64{{126, 126}}, + }, + "TC58": { + MaximizeForDuration: [][2]int64{{25, 25}, {25, 25}, {20, 20}, {20, 20}}, + MinMaxAlgorithm: [][2]int64{{15, 15}, {15, 15}, {15, 20}, {15, 20}, {15, 25}, {15, 25}, {15, 45}, {15, 45}}, + }, + "TC59": { + MaximizeForDuration: [][2]int64{{45, 45}}, + MinMaxAlgorithm: [][2]int64{{30, 30}, {30, 45}}, + }, +} diff --git a/endpoints/openrtb2/ctv/response/adpod_generator copy.go.bak b/endpoints/openrtb2/ctv/response/adpod_generator copy.go.bak new file mode 100644 index 00000000000..c74437e4a18 --- /dev/null +++ b/endpoints/openrtb2/ctv/response/adpod_generator copy.go.bak @@ -0,0 +1,276 @@ +package ctv + +import ( + "context" + "time" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +/********************* AdPodGenerator Functions *********************/ + +//IAdPodGenerator interface for generating AdPod from Ads +type IAdPodGenerator interface { + GetAdPodBids() *AdPodBid +} +type filteredBids struct { + bid *Bid + reasonCode FilterReasonCode +} +type highestCombination struct { + bids []*Bid + price float64 + categoryScore map[string]int + domainScore map[string]int + filteredBids []filteredBids +} + +//AdPodGenerator AdPodGenerator +type AdPodGenerator struct { + IAdPodGenerator + buckets BidsBuckets + comb ICombination + adpod *openrtb_ext.VideoAdPod +} + +//NewAdPodGenerator will generate adpod based on configuration +func NewAdPodGenerator(buckets BidsBuckets, comb ICombination, adpod *openrtb_ext.VideoAdPod) *AdPodGenerator { + return &AdPodGenerator{ + buckets: buckets, + comb: comb, + adpod: adpod, + } +} + +//GetAdPodBids will return Adpod based on configurations +func (o *AdPodGenerator) GetAdPodBids() *AdPodBid { + + isTimedOutORReceivedAllResponses := false + responseCount := 0 + totalRequest := 0 + maxRequests := 5 + responseCh := make(chan *highestCombination, maxRequests) + var results []*highestCombination + + timeout := 50 * time.Millisecond + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + for totalRequest < maxRequests { + durations := o.comb.Get() + if len(durations) == 0 { + break + } + + totalRequest++ + go o.getUniqueBids(responseCh, durations) + } + + for !isTimedOutORReceivedAllResponses { + select { + case <-ctx.Done(): + isTimedOutORReceivedAllResponses = true + case hbc := <-responseCh: + responseCount++ + if nil != hbc { + results = append(results, hbc) + } + if responseCount == totalRequest { + isTimedOutORReceivedAllResponses = true + } + } + } + + go cleanupResponseChannel(responseCh, totalRequest-responseCount) + + if 0 == len(results) { + return nil + } + + //Get Max Response + var maxResult *highestCombination + for _, result := range results { + if nil == maxResult || maxResult.price < result.price { + maxResult = result + } + + for _, rc := range result.filteredBids { + if CTVRCDidNotGetChance == rc.bid.FilterReasonCode { + rc.bid.FilterReasonCode = rc.reasonCode + } + } + } + + adpodBid := &AdPodBid{ + Bids: maxResult.bids[:], + Price: maxResult.price, + ADomain: make([]string, 0), + Cat: make([]string, 0), + } + + //Get Unique Domains + for domain := range maxResult.domainScore { + adpodBid.ADomain = append(adpodBid.ADomain, domain) + } + + //Get Unique Categories + for cat := range maxResult.categoryScore { + adpodBid.Cat = append(adpodBid.Cat, cat) + } + + return adpodBid +} + +func cleanupResponseChannel(responseCh <-chan *highestCombination, responseCount int) { + for responseCount > 0 { + <-responseCh + responseCount-- + } +} + +func (o *AdPodGenerator) getUniqueBids(responseCh chan<- *highestCombination, durationSequence []int) { + data := [][]*Bid{} + combinations := []int{} + + uniqueDuration := 0 + for index, duration := range durationSequence { + if 0 != index && durationSequence[index-1] == duration { + combinations[uniqueDuration-1]++ + continue + } + data = append(data, o.buckets[duration][:]) + combinations = append(combinations, 1) + uniqueDuration++ + } + hbc := findUniqueCombinations(data[:], combinations[:], *o.adpod.IABCategoryExclusionPercent, *o.adpod.AdvertiserExclusionPercent) + responseCh <- hbc +} + +func findUniqueCombinations(data [][]*Bid, combination []int, maxCategoryScore, maxDomainScore int) *highestCombination { + // number of arrays + n := len(combination) + totalBids := 0 + // to keep track of next element in each of the n arrays + // indices is initialized + indices := make([][]int, len(combination)) + for i := 0; i < len(combination); i++ { + indices[i] = make([]int, combination[i]) + for j := 0; j < combination[i]; j++ { + indices[i][j] = j + totalBids++ + } + } + + hc := &highestCombination{} + var ehc *highestCombination + var rc FilterReasonCode + inext, jnext := n-1, 0 + var filterBids []filteredBids + + // maintain highest price combination + for true { + + ehc, inext, jnext, rc = evaluate(data[:], indices[:], totalBids, maxCategoryScore, maxDomainScore) + if nil != ehc { + if nil == hc || hc.price < ehc.price { + hc = ehc + } else { + // if you see current combination price lower than the highest one then break the loop + break + } + } else { + //Filtered Bid + filterBids = append(filterBids, filteredBids{bid: data[inext][indices[inext][jnext]], reasonCode: rc}) + } + + if -1 == inext { + inext, jnext = n-1, 0 + } + + // find the rightmost array that has more + // elements left after the current element + // in that array + inext, jnext := n-1, 0 + + for inext >= 0 { + jnext = len(indices[inext]) - 1 + for jnext >= 0 && (indices[inext][jnext]+1 > (len(data[inext]) - len(indices[inext]) + jnext)) { + jnext-- + } + if jnext >= 0 { + break + } + inext-- + } + + // no such array is found so no more combinations left + if inext < 0 { + break + } + + // if found move to next element in that array + indices[inext][jnext]++ + + // for all arrays to the right of this + // array current index again points to + // first element + jnext++ + for i := inext; i < len(combination); i++ { + for j := jnext; j < combination[i]; j++ { + if i == inext { + indices[i][j] = indices[i][j-1] + 1 + } else { + indices[i][j] = j + } + } + jnext = 0 + } + } + + //setting filteredBids + if nil != hc { + hc.filteredBids = filterBids[:] + } + return hc +} + +func evaluate(bids [][]*Bid, indices [][]int, totalBids int, maxCategoryScore, maxDomainScore int) (*highestCombination, int, int, FilterReasonCode) { + + hbc := &highestCombination{ + bids: make([]*Bid, totalBids), + price: 0, + categoryScore: make(map[string]int), + domainScore: make(map[string]int), + } + pos := 0 + + for inext := range indices { + for jnext := range indices[inext] { + bid := bids[inext][indices[inext][jnext]] + + hbc.bids[pos] = bid + pos++ + + //Price + hbc.price = hbc.price + bid.Price + + //Categories + for _, cat := range bid.Cat { + hbc.categoryScore[cat]++ + if (hbc.categoryScore[cat] * 100 / totalBids) > maxCategoryScore { + return nil, inext, jnext, CTVRCCategoryExclusion + } + } + + //Domain + for _, domain := range bid.ADomain { + hbc.domainScore[domain]++ + if (hbc.domainScore[domain] * 100 / totalBids) > maxDomainScore { + return nil, inext, jnext, CTVRCDomainExclusion + } + } + } + } + + return hbc, -1, -1, CTVRCWinningBid +} diff --git a/endpoints/openrtb2/ctv/response/adpod_generator.go b/endpoints/openrtb2/ctv/response/adpod_generator.go new file mode 100644 index 00000000000..c8bda5da326 --- /dev/null +++ b/endpoints/openrtb2/ctv/response/adpod_generator.go @@ -0,0 +1,397 @@ +package response + +import ( + "fmt" + "sync" + "time" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/combination" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/constant" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +/********************* AdPodGenerator Functions *********************/ + +// IAdPodGenerator interface for generating AdPod from Ads +type IAdPodGenerator interface { + GetAdPodBids() *types.AdPodBid +} +type filteredBid struct { + bid *types.Bid + status constant.BidStatus +} +type highestCombination struct { + bids []*types.Bid + bidIDs []string + durations []int + price float64 + categoryScore map[string]int + domainScore map[string]int + filteredBids map[string]*filteredBid + timeTakenCompExcl time.Duration // time taken by comp excl + timeTakenCombGen time.Duration // time taken by combination generator + nDealBids int +} + +// AdPodGenerator AdPodGenerator +type AdPodGenerator struct { + IAdPodGenerator + request *openrtb2.BidRequest + impIndex int + buckets types.BidsBuckets + comb combination.ICombination + adpod *openrtb_ext.VideoAdPod + met metrics.MetricsEngine +} + +// NewAdPodGenerator will generate adpod based on configuration +func NewAdPodGenerator(request *openrtb2.BidRequest, impIndex int, buckets types.BidsBuckets, comb combination.ICombination, adpod *openrtb_ext.VideoAdPod, met metrics.MetricsEngine) *AdPodGenerator { + return &AdPodGenerator{ + request: request, + impIndex: impIndex, + buckets: buckets, + comb: comb, + adpod: adpod, + met: met, + } +} + +// GetAdPodBids will return Adpod based on configurations +func (o *AdPodGenerator) GetAdPodBids() *types.AdPodBid { + defer util.TimeTrack(time.Now(), fmt.Sprintf("Tid:%v ImpId:%v adpodgenerator", o.request.ID, o.request.Imp[o.impIndex].ID)) + + results := o.getAdPodBids(10 * time.Millisecond) + adpodBid := o.getMaxAdPodBid(results) + + return adpodBid +} + +func (o *AdPodGenerator) cleanup(wg *sync.WaitGroup, responseCh chan *highestCombination) { + defer func() { + close(responseCh) + for extra := range responseCh { + if nil != extra { + util.Logf("Tid:%v ImpId:%v Delayed Response Durations:%v Bids:%v", o.request.ID, o.request.Imp[o.impIndex].ID, extra.durations, extra.bidIDs) + } + } + }() + wg.Wait() +} + +func (o *AdPodGenerator) getAdPodBids(timeout time.Duration) []*highestCombination { + start := time.Now() + defer util.TimeTrack(start, fmt.Sprintf("Tid:%v ImpId:%v getAdPodBids", o.request.ID, o.request.Imp[o.impIndex].ID)) + + maxRoutines := 2 + isTimedOutORReceivedAllResponses := false + results := []*highestCombination{} + responseCh := make(chan *highestCombination, maxRoutines) + wg := new(sync.WaitGroup) // ensures each step generating impressions is finished + lock := sync.Mutex{} + ticker := time.NewTicker(timeout) + combGenStartTime := time.Now() + lock.Lock() + durations := o.comb.Get() + lock.Unlock() + combGenElapsedTime := time.Since(combGenStartTime) + + if len(durations) != 0 { + hbc := o.getUniqueBids(durations) + hbc.timeTakenCombGen = combGenElapsedTime + responseCh <- hbc + util.Logf("Tid:%v GetUniqueBids Durations:%v Price:%v DealBids:%v Time:%v Bids:%v combGenElapsedTime:%v", o.request.ID, hbc.durations[:], hbc.price, hbc.nDealBids, hbc.timeTakenCompExcl, hbc.bidIDs[:], combGenElapsedTime) + } + combinationCount := 0 + for i := 0; i < maxRoutines; i++ { + wg.Add(1) + go func() { + for !isTimedOutORReceivedAllResponses { + combGenStartTime := time.Now() + lock.Lock() + durations := o.comb.Get() + lock.Unlock() + combGenElapsedTime := time.Since(combGenStartTime) + + if len(durations) == 0 { + break + } + hbc := o.getUniqueBids(durations) + hbc.timeTakenCombGen = combGenElapsedTime + responseCh <- hbc + util.Logf("Tid:%v GetUniqueBids Durations:%v Price:%v DealBids:%v Time:%v Bids:%v combGenElapsedTime:%v", o.request.ID, hbc.durations[:], hbc.price, hbc.nDealBids, hbc.timeTakenCompExcl, hbc.bidIDs[:], combGenElapsedTime) + } + wg.Done() + }() + } + + // ensure impressions channel is closed + // when all go routines are executed + go o.cleanup(wg, responseCh) + + totalTimeByCombGen := int64(0) + totalTimeByCompExcl := int64(0) + for !isTimedOutORReceivedAllResponses { + select { + case hbc, ok := <-responseCh: + + if false == ok { + isTimedOutORReceivedAllResponses = true + break + } + if nil != hbc { + combinationCount++ + totalTimeByCombGen += int64(hbc.timeTakenCombGen) + totalTimeByCompExcl += int64(hbc.timeTakenCompExcl) + results = append(results, hbc) + } + case <-ticker.C: + isTimedOutORReceivedAllResponses = true + util.Logf("Tid:%v ImpId:%v GetAdPodBids Timeout Reached %v", o.request.ID, o.request.Imp[o.impIndex].ID, timeout) + } + } + + defer ticker.Stop() + + labels := metrics.PodLabels{ + AlgorithmName: string(constant.CombinationGeneratorV1), + NoOfCombinations: new(int), + } + *labels.NoOfCombinations = combinationCount + o.met.RecordPodCombGenTime(labels, time.Duration(totalTimeByCombGen)) + + compExclLabels := metrics.PodLabels{ + AlgorithmName: string(constant.CompetitiveExclusionV1), + NoOfResponseBids: new(int), + } + *compExclLabels.NoOfResponseBids = 0 + for _, ads := range o.buckets { + *compExclLabels.NoOfResponseBids += len(ads) + } + o.met.RecordPodCompititveExclusionTime(compExclLabels, time.Duration(totalTimeByCompExcl)) + + return results[:] +} + +func (o *AdPodGenerator) getMaxAdPodBid(results []*highestCombination) *types.AdPodBid { + if 0 == len(results) { + util.Logf("Tid:%v ImpId:%v NoBid", o.request.ID, o.request.Imp[o.impIndex].ID) + return nil + } + + //Get Max Response + var maxResult *highestCombination + for _, result := range results { + for _, rc := range result.filteredBids { + if constant.StatusOK == rc.bid.Status { + rc.bid.Status = rc.status + } + } + if len(result.bidIDs) == 0 { + continue + } + + if nil == maxResult || + (maxResult.nDealBids < result.nDealBids) || + (maxResult.nDealBids == result.nDealBids && maxResult.price < result.price) { + maxResult = result + } + } + + if nil == maxResult { + util.Logf("Tid:%v ImpId:%v All Combination Filtered in Ad Exclusion", o.request.ID, o.request.Imp[o.impIndex].ID) + return nil + } + + adpodBid := &types.AdPodBid{ + Bids: maxResult.bids[:], + Price: maxResult.price, + ADomain: make([]string, 0), + Cat: make([]string, 0), + } + + //Get Unique Domains + for domain := range maxResult.domainScore { + adpodBid.ADomain = append(adpodBid.ADomain, domain) + } + + //Get Unique Categories + for cat := range maxResult.categoryScore { + adpodBid.Cat = append(adpodBid.Cat, cat) + } + + util.Logf("Tid:%v ImpId:%v Selected Durations:%v Price:%v Bids:%v", o.request.ID, o.request.Imp[o.impIndex].ID, maxResult.durations[:], maxResult.price, maxResult.bidIDs[:]) + + return adpodBid +} + +func (o *AdPodGenerator) getUniqueBids(durationSequence []int) *highestCombination { + startTime := time.Now() + data := [][]*types.Bid{} + combinations := []int{} + + defer util.TimeTrack(startTime, fmt.Sprintf("Tid:%v ImpId:%v getUniqueBids:%v", o.request.ID, o.request.Imp[o.impIndex].ID, durationSequence)) + + uniqueDuration := 0 + for index, duration := range durationSequence { + if 0 != index && durationSequence[index-1] == duration { + combinations[uniqueDuration-1]++ + continue + } + data = append(data, o.buckets[duration][:]) + combinations = append(combinations, 1) + uniqueDuration++ + } + hbc := findUniqueCombinations(data[:], combinations[:], *o.adpod.IABCategoryExclusionPercent, *o.adpod.AdvertiserExclusionPercent) + hbc.durations = durationSequence[:] + hbc.timeTakenCompExcl = time.Since(startTime) + + return hbc +} + +func findUniqueCombinations(data [][]*types.Bid, combination []int, maxCategoryScore, maxDomainScore int) *highestCombination { + // number of arrays + n := len(combination) + totalBids := 0 + // to keep track of next element in each of the n arrays + // indices is initialized + indices := make([][]int, len(combination)) + for i := 0; i < len(combination); i++ { + indices[i] = make([]int, combination[i]) + for j := 0; j < combination[i]; j++ { + indices[i][j] = j + totalBids++ + } + } + + hc := &highestCombination{} + var ehc *highestCombination + var rc constant.BidStatus + inext, jnext := n-1, 0 + filterBids := map[string]*filteredBid{} + + // maintain highest price combination + for true { + + ehc, inext, jnext, rc = evaluate(data[:], indices[:], totalBids, maxCategoryScore, maxDomainScore) + if nil != ehc { + if nil == hc || (hc.nDealBids == ehc.nDealBids && hc.price < ehc.price) || (hc.nDealBids < ehc.nDealBids) { + hc = ehc + } else { + // if you see current combination price lower than the highest one then break the loop + break + } + } else { + //Filtered Bid + for i := 0; i <= inext; i++ { + for j := 0; j < combination[i] && !(i == inext && j > jnext); j++ { + bid := data[i][indices[i][j]] + if _, ok := filterBids[bid.ID]; !ok { + filterBids[bid.ID] = &filteredBid{bid: bid, status: rc} + } + } + } + } + + if -1 == inext { + inext, jnext = n-1, 0 + } + + // find the rightmost array that has more + // elements left after the current element + // in that array + inext, jnext := n-1, 0 + + for inext >= 0 { + jnext = len(indices[inext]) - 1 + for jnext >= 0 && (indices[inext][jnext]+1 > (len(data[inext]) - len(indices[inext]) + jnext)) { + jnext-- + } + if jnext >= 0 { + break + } + inext-- + } + + // no such array is found so no more combinations left + if inext < 0 { + break + } + + // if found move to next element in that array + indices[inext][jnext]++ + + // for all arrays to the right of this + // array current index again points to + // first element + jnext++ + for i := inext; i < len(combination); i++ { + for j := jnext; j < combination[i]; j++ { + if i == inext { + indices[i][j] = indices[i][j-1] + 1 + } else { + indices[i][j] = j + } + } + jnext = 0 + } + } + + //setting filteredBids + if nil != filterBids { + hc.filteredBids = filterBids + } + return hc +} + +func evaluate(bids [][]*types.Bid, indices [][]int, totalBids int, maxCategoryScore, maxDomainScore int) (*highestCombination, int, int, constant.BidStatus) { + + hbc := &highestCombination{ + bids: make([]*types.Bid, totalBids), + bidIDs: make([]string, totalBids), + price: 0, + categoryScore: make(map[string]int), + domainScore: make(map[string]int), + nDealBids: 0, + } + pos := 0 + + for inext := range indices { + for jnext := range indices[inext] { + bid := bids[inext][indices[inext][jnext]] + + hbc.bids[pos] = bid + hbc.bidIDs[pos] = bid.ID + pos++ + + //nDealBids + if bid.DealTierSatisfied { + hbc.nDealBids++ + } + + //Price + hbc.price = hbc.price + bid.Price + + //Categories + for _, cat := range bid.Cat { + hbc.categoryScore[cat]++ + if hbc.categoryScore[cat] > 1 && (hbc.categoryScore[cat]*100/totalBids) > maxCategoryScore { + return nil, inext, jnext, constant.StatusCategoryExclusion + } + } + + //Domain + for _, domain := range bid.ADomain { + hbc.domainScore[domain]++ + if hbc.domainScore[domain] > 1 && (hbc.domainScore[domain]*100/totalBids) > maxDomainScore { + return nil, inext, jnext, constant.StatusDomainExclusion + } + } + } + } + + return hbc, -1, -1, constant.StatusWinningBid +} diff --git a/endpoints/openrtb2/ctv/response/adpod_generator_test.go b/endpoints/openrtb2/ctv/response/adpod_generator_test.go new file mode 100644 index 00000000000..24188fdd531 --- /dev/null +++ b/endpoints/openrtb2/ctv/response/adpod_generator_test.go @@ -0,0 +1,397 @@ +package response + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/constant" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/types" +) + +func Test_findUniqueCombinations(t *testing.T) { + type args struct { + data [][]*types.Bid + combination []int + maxCategoryScore int + maxDomainScore int + } + tests := []struct { + name string + args args + want *highestCombination + }{ + { + name: "sample", + args: args{ + data: [][]*types.Bid{ + { + { + Bid: &openrtb2.Bid{ID: "3-ed72b572-ba62-4220-abba-c19c0bf6346b", Price: 6.339115524232314}, + DealTierSatisfied: true, + }, + { + Bid: &openrtb2.Bid{ID: "4-ed72b572-ba62-4220-abba-c19c0bf6346b", Price: 3.532468782358357}, + DealTierSatisfied: true, + }, + { + Bid: &openrtb2.Bid{ID: "7-VIDEO12-89A1-41F1-8708-978FD3C0912A", Price: 5}, + DealTierSatisfied: false, + }, + { + Bid: &openrtb2.Bid{ID: "8-VIDEO12-89A1-41F1-8708-978FD3C0912A", Price: 5}, + DealTierSatisfied: false, + }, + }, //20 + + { + { + Bid: &openrtb2.Bid{ID: "2-ed72b572-ba62-4220-abba-c19c0bf6346b", Price: 3.4502433547413878}, + DealTierSatisfied: true, + }, + { + Bid: &openrtb2.Bid{ID: "1-ed72b572-ba62-4220-abba-c19c0bf6346b", Price: 3.329644588311827}, + DealTierSatisfied: true, + }, + { + Bid: &openrtb2.Bid{ID: "5-VIDEO12-89A1-41F1-8708-978FD3C0912A", Price: 5}, + DealTierSatisfied: false, + }, + { + Bid: &openrtb2.Bid{ID: "6-VIDEO12-89A1-41F1-8708-978FD3C0912A", Price: 5}, + DealTierSatisfied: false, + }, + }, //25 + }, + + combination: []int{2, 2}, + maxCategoryScore: 100, + maxDomainScore: 100, + }, + want: &highestCombination{ + bidIDs: []string{"3-ed72b572-ba62-4220-abba-c19c0bf6346b", "4-ed72b572-ba62-4220-abba-c19c0bf6346b", "2-ed72b572-ba62-4220-abba-c19c0bf6346b", "1-ed72b572-ba62-4220-abba-c19c0bf6346b"}, + price: 16.651472249643884, + nDealBids: 4, + }, + }, + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := findUniqueCombinations(tt.args.data, tt.args.combination, tt.args.maxCategoryScore, tt.args.maxDomainScore) + assert.Equal(t, tt.want.bidIDs, got.bidIDs, "bidIDs") + assert.Equal(t, tt.want.nDealBids, got.nDealBids, "nDealBids") + assert.Equal(t, tt.want.price, got.price, "price") + }) + } +} + +func TestAdPodGenerator_getMaxAdPodBid(t *testing.T) { + type fields struct { + request *openrtb2.BidRequest + impIndex int + } + type args struct { + results []*highestCombination + } + tests := []struct { + name string + fields fields + args args + want *types.AdPodBid + }{ + { + name: `EmptyResults`, + fields: fields{ + request: &openrtb2.BidRequest{ID: `req-1`, Imp: []openrtb2.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: nil, + }, + want: nil, + }, + { + name: `AllBidsFiltered`, + fields: fields{ + request: &openrtb2.BidRequest{ID: `req-1`, Imp: []openrtb2.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: []*highestCombination{ + { + filteredBids: map[string]*filteredBid{ + `bid-1`: {bid: &types.Bid{Bid: &openrtb2.Bid{ID: `bid-1`}}, status: constant.StatusCategoryExclusion}, + `bid-2`: {bid: &types.Bid{Bid: &openrtb2.Bid{ID: `bid-2`}}, status: constant.StatusCategoryExclusion}, + `bid-3`: {bid: &types.Bid{Bid: &openrtb2.Bid{ID: `bid-3`}}, status: constant.StatusCategoryExclusion}, + }, + }, + }, + }, + want: nil, + }, + { + name: `SingleResponse`, + fields: fields{ + request: &openrtb2.BidRequest{ID: `req-1`, Imp: []openrtb2.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: []*highestCombination{ + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-1`}}, + {Bid: &openrtb2.Bid{ID: `bid-2`}}, + {Bid: &openrtb2.Bid{ID: `bid-3`}}, + }, + bidIDs: []string{`bid-1`, `bid-2`, `bid-3`}, + price: 20, + nDealBids: 0, + categoryScore: map[string]int{ + `cat-1`: 1, + `cat-2`: 1, + }, + domainScore: map[string]int{ + `domain-1`: 1, + `domain-2`: 1, + }, + filteredBids: map[string]*filteredBid{ + `bid-4`: {bid: &types.Bid{Bid: &openrtb2.Bid{ID: `bid-4`}}, status: constant.StatusCategoryExclusion}, + }, + }, + }, + }, + want: &types.AdPodBid{ + Bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-1`}}, + {Bid: &openrtb2.Bid{ID: `bid-2`}}, + {Bid: &openrtb2.Bid{ID: `bid-3`}}, + }, + Cat: []string{`cat-1`, `cat-2`}, + ADomain: []string{`domain-1`, `domain-2`}, + Price: 20, + }, + }, + { + name: `MultiResponse-AllNonDealBids`, + fields: fields{ + request: &openrtb2.BidRequest{ID: `req-1`, Imp: []openrtb2.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: []*highestCombination{ + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-11`}}, + }, + bidIDs: []string{`bid-11`}, + price: 10, + nDealBids: 0, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-21`}}, + }, + bidIDs: []string{`bid-21`}, + price: 20, + nDealBids: 0, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-31`}}, + }, + bidIDs: []string{`bid-31`}, + price: 10, + nDealBids: 0, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-41`}}, + }, + bidIDs: []string{`bid-41`}, + price: 15, + nDealBids: 0, + }, + }, + }, + want: &types.AdPodBid{ + Bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-21`}}, + }, + Cat: []string{}, + ADomain: []string{}, + Price: 20, + }, + }, + { + name: `MultiResponse-AllDealBids-SameCount`, + fields: fields{ + request: &openrtb2.BidRequest{ID: `req-1`, Imp: []openrtb2.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: []*highestCombination{ + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-11`}}, + }, + bidIDs: []string{`bid-11`}, + price: 10, + nDealBids: 1, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-21`}}, + }, + bidIDs: []string{`bid-21`}, + price: 20, + nDealBids: 1, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-31`}}, + }, + bidIDs: []string{`bid-31`}, + price: 10, + nDealBids: 1, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-41`}}, + }, + bidIDs: []string{`bid-41`}, + price: 15, + nDealBids: 1, + }, + }, + }, + want: &types.AdPodBid{ + Bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-21`}}, + }, + Cat: []string{}, + ADomain: []string{}, + Price: 20, + }, + }, + { + name: `MultiResponse-AllDealBids-DifferentCount`, + fields: fields{ + request: &openrtb2.BidRequest{ID: `req-1`, Imp: []openrtb2.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: []*highestCombination{ + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-11`}}, + }, + bidIDs: []string{`bid-11`}, + price: 10, + nDealBids: 2, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-21`}}, + }, + bidIDs: []string{`bid-21`}, + price: 20, + nDealBids: 1, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-31`}}, + }, + bidIDs: []string{`bid-31`}, + price: 10, + nDealBids: 3, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-41`}}, + }, + bidIDs: []string{`bid-41`}, + price: 15, + nDealBids: 2, + }, + }, + }, + want: &types.AdPodBid{ + Bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-31`}}, + }, + Cat: []string{}, + ADomain: []string{}, + Price: 10, + }, + }, + { + name: `MultiResponse-Mixed-DealandNonDealBids`, + fields: fields{ + request: &openrtb2.BidRequest{ID: `req-1`, Imp: []openrtb2.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: []*highestCombination{ + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-11`}}, + }, + bidIDs: []string{`bid-11`}, + price: 10, + nDealBids: 2, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-21`}}, + }, + bidIDs: []string{`bid-21`}, + price: 20, + nDealBids: 0, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-31`}}, + }, + bidIDs: []string{`bid-31`}, + price: 10, + nDealBids: 3, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-41`}}, + }, + bidIDs: []string{`bid-41`}, + price: 15, + nDealBids: 0, + }, + }, + }, + want: &types.AdPodBid{ + Bids: []*types.Bid{ + {Bid: &openrtb2.Bid{ID: `bid-31`}}, + }, + Cat: []string{}, + ADomain: []string{}, + Price: 10, + }, + }, + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &AdPodGenerator{ + request: tt.fields.request, + impIndex: tt.fields.impIndex, + } + got := o.getMaxAdPodBid(tt.args.results) + if nil != got { + sort.Strings(got.ADomain) + sort.Strings(got.Cat) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/endpoints/openrtb2/ctv/response/adpod_generator_test.go.bak b/endpoints/openrtb2/ctv/response/adpod_generator_test.go.bak new file mode 100644 index 00000000000..fa3590a1e16 --- /dev/null +++ b/endpoints/openrtb2/ctv/response/adpod_generator_test.go.bak @@ -0,0 +1,124 @@ +package ctv + +/* +func (o *AdPodGenerator) getUniqueBids(responseCh chan<- *highestCombination, durationSequence []int) { + data := [][]*Bid{} + combinations := []int{} + + for index, duration := range durationSequence { + data[index] = o.buckets[duration][:] + } + + responseCh <- findUniqueCombinations(data[:], *o.adpod.IABCategoryExclusionPercent, *o.adpod.AdvertiserExclusionPercent) +} +*/ + +// Todo: this function is still returning (B3 B4) and (B4 B3), need to work on it +// func findUniqueCombinations(arr [][]Bid) ([][]Bid) { +func findUniqueCombinationsOld(arr [][]*Bid, maxCategoryScore, maxDomainScore int) *highestCombination { + // number of arrays + n := len(arr) + // to keep track of next element in each of the n arrays + indices := make([]int, n) + // indices is initialized with all zeros + + // maintain highest price combination + var ehc *highestCombination + var rc FilterReasonCode + next := n - 1 + hc := &highestCombination{price: 0} + for true { + + row := []*Bid{} + // We do not want the same bid to appear twice in a combination + bidsInRow := make(map[string]bool) + good := true + + for i := 0; i < n; i++ { + if _, present := bidsInRow[arr[i][indices[i]].ID]; !present { + row = append(row, arr[i][indices[i]]) + bidsInRow[arr[i][indices[i]].ID] = true + } else { + good = false + break + } + } + + if good { + // output = append(output, row) + // give a call for exclusion checking here only + ehc, next, rc = evaluateOld(row, maxCategoryScore, maxDomainScore) + if nil != ehc { + if nil == hc || hc.price < ehc.price { + hc = ehc + } else { + // if you see current combination price lower than the highest one then break the loop + return hc + } + } else { + arr[next][indices[next]].FilterReasonCode = rc + } + } + + // find the rightmost array that has more + // elements left after the current element + // in that array + if -1 == next { + next = n - 1 + } + + for next >= 0 && (indices[next]+1 >= len(arr[next])) { + next-- + } + + // no such array is found so no more combinations left + if next < 0 { + // return output + return nil + } + + // if found move to next element in that array + indices[next]++ + + // for all arrays to the right of this + // array current index again points to + // first element + for i := next + 1; i < n; i++ { + indices[i] = 0 + } + } + // return output + return hc +} + +func evaluateOld(bids []*Bid, maxCategoryScore, maxDomainScore int) (*highestCombination, int, FilterReasonCode) { + + hbc := &highestCombination{ + bids: bids, + price: 0, + categoryScore: make(map[string]int), + domainScore: make(map[string]int), + } + + totalBids := len(bids) + + for index, bid := range bids { + hbc.price = hbc.price + bid.Price + + for _, cat := range bid.Cat { + hbc.categoryScore[cat]++ + if (hbc.categoryScore[cat] * 100 / totalBids) > maxCategoryScore { + return nil, index, CTVRCCategoryExclusion + } + } + + for _, domain := range bid.ADomain { + hbc.domainScore[domain]++ + if (hbc.domainScore[domain] * 100 / totalBids) > maxDomainScore { + return nil, index, CTVRCDomainExclusion + } + } + } + + return hbc, -1, CTVRCWinningBid +} diff --git a/endpoints/openrtb2/ctv/types/adpod_types.go b/endpoints/openrtb2/ctv/types/adpod_types.go new file mode 100644 index 00000000000..224b123e355 --- /dev/null +++ b/endpoints/openrtb2/ctv/types/adpod_types.go @@ -0,0 +1,64 @@ +package types + +import ( + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/constant" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// Bid openrtb bid object with extra parameters +type Bid struct { + *openrtb2.Bid + openrtb_ext.ExtBid + Duration int + Status constant.BidStatus + DealTierSatisfied bool + Seat string +} + +// ExtCTVBidResponse object for ctv bid resposne object +type ExtCTVBidResponse struct { + openrtb_ext.ExtBidResponse + AdPod *BidResponseAdPodExt `json:"adpod,omitempty"` +} + +// BidResponseAdPodExt object for ctv bidresponse adpod object +type BidResponseAdPodExt struct { + Response openrtb2.BidResponse `json:"bidresponse,omitempty"` + Config map[string]*ImpData `json:"config,omitempty"` +} + +// AdPodBid combination contains ImpBid +type AdPodBid struct { + Bids []*Bid + Price float64 + Cat []string + ADomain []string + OriginalImpID string + SeatName string +} + +// AdPodBids combination contains ImpBid +type AdPodBids []*AdPodBid + +// BidsBuckets bids bucket +type BidsBuckets map[int][]*Bid + +// ImpAdPodConfig configuration for creating ads in adpod +type ImpAdPodConfig struct { + ImpID string `json:"id,omitempty"` + SequenceNumber int8 `json:"seq,omitempty"` + MinDuration int64 `json:"minduration,omitempty"` + MaxDuration int64 `json:"maxduration,omitempty"` +} + +// ImpData example +type ImpData struct { + //AdPodGenerator + ImpID string `json:"-"` + Bid *AdPodBid `json:"-"` + VideoExt *openrtb_ext.ExtVideoAdPod `json:"vidext,omitempty"` + Config []*ImpAdPodConfig `json:"imp,omitempty"` + BlockedVASTTags map[string][]string `json:"blockedtags,omitempty"` + Error *openrtb_ext.ExtBidderMessage `json:"ec,omitempty"` +} diff --git a/endpoints/openrtb2/ctv/util/util.go b/endpoints/openrtb2/ctv/util/util.go new file mode 100644 index 00000000000..108be4c700f --- /dev/null +++ b/endpoints/openrtb2/ctv/util/util.go @@ -0,0 +1,141 @@ +package util + +import ( + "encoding/json" + "fmt" + "math" + "sort" + "strconv" + "strings" + "time" + + "github.com/buger/jsonparser" + "github.com/golang/glog" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/constant" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +var ( + //prebid_ctv_errors + UnableToGenerateImpressionsError = &errortypes.AdpodPrefiltering{Message: `prebid_ctv unable to generate impressions for adpod`} + + //prebid_ctv_warnings + DurationMismatchWarning = &openrtb_ext.ExtBidderMessage{Code: errortypes.AdpodPostFilteringWarningCode, Message: `prebid_ctv all bids filtered while matching lineitem duration`} + UnableToGenerateAdPodWarning = &openrtb_ext.ExtBidderMessage{Code: errortypes.AdpodPostFilteringWarningCode, Message: `prebid_ctv unable to generate adpod from bids combinations`} +) + +func GetDurationWiseBidsBucket(bids []*types.Bid) types.BidsBuckets { + result := types.BidsBuckets{} + + for i, bid := range bids { + if constant.StatusOK == bid.Status { + result[bid.Duration] = append(result[bid.Duration], bids[i]) + } + } + + for k, v := range result { + //sort.Slice(v[:], func(i, j int) bool { return v[i].Price > v[j].Price }) + sortBids(v[:]) + result[k] = v + } + + return result +} + +func sortBids(bids []*types.Bid) { + sort.Slice(bids, func(i, j int) bool { + if bids[i].DealTierSatisfied == bids[j].DealTierSatisfied { + return bids[i].Price > bids[j].Price + } + return bids[i].DealTierSatisfied + }) +} + +// GetDealTierSatisfied ... +func GetDealTierSatisfied(ext *openrtb_ext.ExtBid) bool { + return ext != nil && ext.Prebid != nil && ext.Prebid.DealTierSatisfied +} + +func DecodeImpressionID(id string) (string, int) { + index := strings.LastIndex(id, constant.CTVImpressionIDSeparator) + if index == -1 { + return id, 0 + } + + sequence, err := strconv.Atoi(id[index+1:]) + if nil != err || 0 == sequence { + return id, 0 + } + + return id[:index], sequence +} + +func GetCTVImpressionID(impID string, seqNo int) string { + return fmt.Sprintf(constant.CTVImpressionIDFormat, impID, seqNo) +} + +func GetUniqueBidID(bidID string, id int) string { + return fmt.Sprintf(constant.CTVUniqueBidIDFormat, id, bidID) +} + +var Logf = func(msg string, args ...interface{}) { + if glog.V(3) { + glog.Infof(msg, args...) + } + //fmt.Printf(msg+"\n", args...) +} + +func JLogf(msg string, obj interface{}) { + if glog.V(3) { + data, _ := json.Marshal(obj) + glog.Infof("[OPENWRAP] %v:%v", msg, string(data)) + } +} + +func TimeTrack(start time.Time, name string) { + elapsed := time.Since(start) + Logf("[TIMETRACK] %s took %s", name, elapsed) + //eg: defer TimeTrack(time.Now(), "factorial") +} + +// GetTargeting returns the value of targeting key associated with bidder +// it is expected that bid.Ext contains prebid.targeting map +// if value not present or any error occured empty value will be returned +// along with error. +func GetTargeting(key openrtb_ext.TargetingKey, bidder openrtb_ext.BidderName, bid openrtb2.Bid) (string, error) { + bidderSpecificKey := key.BidderKey(openrtb_ext.BidderName(bidder), 20) + return jsonparser.GetString(bid.Ext, "prebid", "targeting", bidderSpecificKey) +} + +// GetNearestDuration will return nearest duration value present in ImpAdPodConfig objects +// it will return -1 if it doesn't found any match +func GetNearestDuration(duration int64, config []*types.ImpAdPodConfig) int64 { + tmp := int64(-1) + diff := int64(math.MaxInt64) + for _, c := range config { + tdiff := (c.MaxDuration - duration) + if tdiff == 0 { + tmp = c.MaxDuration + break + } + if tdiff > 0 && tdiff <= diff { + tmp = c.MaxDuration + diff = tdiff + } + } + return tmp +} + +// ErrToBidderMessage will return error message in ExtBidderMessage format +func ErrToBidderMessage(err error) *openrtb_ext.ExtBidderMessage { + if err == nil { + return nil + } + return &openrtb_ext.ExtBidderMessage{ + Code: errortypes.ReadCode(err), + Message: err.Error(), + } +} diff --git a/endpoints/openrtb2/ctv/util/util_test.go b/endpoints/openrtb2/ctv/util/util_test.go new file mode 100644 index 00000000000..e9a57e3f608 --- /dev/null +++ b/endpoints/openrtb2/ctv/util/util_test.go @@ -0,0 +1,342 @@ +package util + +import ( + "fmt" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestDecodeImpressionID(t *testing.T) { + type args struct { + id string + } + type want struct { + id string + seq int + } + tests := []struct { + name string + args args + want want + }{ + { + name: "TC1", + args: args{id: "impid"}, + want: want{id: "impid", seq: 0}, + }, + { + name: "TC2", + args: args{id: "impid_1"}, + want: want{id: "impid", seq: 1}, + }, + { + name: "TC1", + args: args{id: "impid_1_2"}, + want: want{id: "impid_1", seq: 2}, + }, + { + name: "TC1", + args: args{id: "impid_1_x"}, + want: want{id: "impid_1_x", seq: 0}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + id, seq := DecodeImpressionID(tt.args.id) + assert.Equal(t, tt.want.id, id) + assert.Equal(t, tt.want.seq, seq) + }) + } +} + +func TestSortByDealPriority(t *testing.T) { + + type testbid struct { + id string + price float64 + isDealBid bool + } + + testcases := []struct { + scenario string + bids []testbid + expectedBidIDOrdering []string + }{ + /* tests based on truth table */ + { + scenario: "all_deal_bids_do_price_based_sort", + bids: []testbid{ + {id: "DB_$5", price: 5.0, isDealBid: true}, // Deal bid with low price + {id: "DB_$10", price: 10.0, isDealBid: true}, // Deal bid with high price + }, + expectedBidIDOrdering: []string{"DB_$10", "DB_$5"}, // sort by price among deal bids + }, + { + scenario: "normal_and_deal_bid_mix_case_1", + bids: []testbid{ + {id: "DB_$15", price: 15.0, isDealBid: true}, // Deal bid with low price + {id: "B_$30", price: 30.0, isDealBid: false}, // Normal bid with high price + }, + expectedBidIDOrdering: []string{"DB_$15", "B_$30"}, // no sort expected. Deal bid is already 1st in order + }, + { + scenario: "normal_and_deal_bid_mix_case_2", // deal bids are not at start position in order + bids: []testbid{ + {id: "B_$30", price: 30.0, isDealBid: false}, // Normal bid with high price + {id: "DB_$15", price: 15.0, isDealBid: true}, // Deal bid with low price + }, + expectedBidIDOrdering: []string{"DB_$15", "B_$30"}, // sort based on deal bid + }, + { + scenario: "all_normal_bids_sort_by_price_case_1", + bids: []testbid{ + {id: "B_$5", price: 5.0, isDealBid: false}, + {id: "B_$10", price: 10.0, isDealBid: false}, + }, + expectedBidIDOrdering: []string{"B_$10", "B_$5"}, // sort by price + }, + { + scenario: "all_normal_bids_sort_by_price_case_2", // already sorted by highest price + bids: []testbid{ + {id: "B_$10", price: 10.0, isDealBid: false}, + {id: "B_$5", price: 5.0, isDealBid: false}, + }, + expectedBidIDOrdering: []string{"B_$10", "B_$5"}, // no sort required as already sorted + }, + /* use cases */ + { + scenario: "deal_bids_with_same_price", + bids: []testbid{ + {id: "DB2_$10", price: 10.0, isDealBid: true}, + {id: "DB1_$10", price: 10.0, isDealBid: true}, + }, + expectedBidIDOrdering: []string{"DB2_$10", "DB1_$10"}, // no sort expected + }, + /* more than 2 Bids testcases */ + { + scenario: "4_bids_with_first_and_last_are_deal_bids", + bids: []testbid{ + {id: "DB_$15", price: 15.0, isDealBid: true}, // deal bid with low CPM than another bid + {id: "B_$40", price: 40.0, isDealBid: false}, // normal bid with highest CPM + {id: "B_$3", price: 3.0, isDealBid: false}, + {id: "DB_$20", price: 20.0, isDealBid: true}, // deal bid with high cpm than another deal bid + }, + expectedBidIDOrdering: []string{"DB_$20", "DB_$15", "B_$40", "B_$3"}, + }, + { + scenario: "deal_bids_and_normal_bids_with_same_price", + bids: []testbid{ + {id: "B1_$7", price: 7.0, isDealBid: false}, + {id: "DB2_$7", price: 7.0, isDealBid: true}, + {id: "B3_$7", price: 7.0, isDealBid: false}, + {id: "DB1_$7", price: 7.0, isDealBid: true}, + {id: "B2_$7", price: 7.0, isDealBid: false}, + }, + expectedBidIDOrdering: []string{"DB2_$7", "DB1_$7", "B1_$7", "B3_$7", "B2_$7"}, // no sort expected + }, + } + + newBid := func(bid testbid) *types.Bid { + return &types.Bid{ + Bid: &openrtb2.Bid{ + ID: bid.id, + Price: bid.price, + //Ext: json.RawMessage(`{"prebid":{ "dealTierSatisfied" : ` + bid.isDealBid + ` }}`), + }, + DealTierSatisfied: bid.isDealBid, + } + } + + for _, test := range testcases { + // if test.scenario != "deal_bids_and_normal_bids_with_same_price" { + // continue + // } + fmt.Println("Scenario : ", test.scenario) + bids := []*types.Bid{} + for _, bid := range test.bids { + bids = append(bids, newBid(bid)) + } + for _, bid := range bids { + fmt.Println(bid.ID, ",", bid.Price, ",", bid.DealTierSatisfied) + } + sortBids(bids[:]) + fmt.Println("After sort") + actual := []string{} + for _, bid := range bids { + fmt.Println(bid.ID, ",", bid.Price, ", ", bid.DealTierSatisfied) + actual = append(actual, bid.ID) + } + assert.Equal(t, test.expectedBidIDOrdering, actual, test.scenario+" failed") + fmt.Println("") + } +} + +func TestGetTargeting(t *testing.T) { + var tests = []struct { + scenario string // Testcase scenario + targeting string + bidder string + key openrtb_ext.TargetingKey + expectValue string + expectError bool + }{ + {"no hb_bidder, expect error", "", "", openrtb_ext.HbCategoryDurationKey, "", true}, + {"hb_bidder present, no key present", `{"x" : "y"}`, "appnexus", openrtb_ext.HbCategoryDurationKey, "", true}, + {"hb_bidder present, required key present (of length 20)", `{"x" : "y", "hb_pb_cat_dur_appnex" : "5.00_sports_10s"}`, "appnexus", openrtb_ext.HbCategoryDurationKey, "5.00_sports_10s", false}, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + bid := new(openrtb2.Bid) + bid.Ext = []byte(`{"prebid" : { "targeting" : ` + test.targeting + `}}`) + value, err := GetTargeting(test.key, openrtb_ext.BidderName(test.bidder), *bid) + if test.expectError { + assert.NotNil(t, err) + assert.Empty(t, value) + } + assert.Equal(t, test.expectValue, value) + }) + } +} + +func TestGetNearestDuration(t *testing.T) { + type args struct { + duration int64 + config []*types.ImpAdPodConfig + } + tests := []struct { + name string + args args + wantDuration int64 + }{ + // TODO: Add test cases. + { + name: "sorted_array_exact_match", + args: args{ + duration: 20, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 10}, + {MaxDuration: 20}, + {MaxDuration: 30}, + {MaxDuration: 40}, + }, + }, + wantDuration: 20, + }, + { + name: "sorted_array_first_element", + args: args{ + duration: 5, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 10}, + {MaxDuration: 20}, + {MaxDuration: 30}, + {MaxDuration: 40}, + }, + }, + wantDuration: 10, + }, + { + name: "sorted_array_not_found", + args: args{ + duration: 45, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 10}, + {MaxDuration: 20}, + {MaxDuration: 30}, + {MaxDuration: 40}, + }, + }, + wantDuration: -1, + }, + { + name: "unsorted_array_exact_match", + args: args{ + duration: 10, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 40}, + {MaxDuration: 20}, + {MaxDuration: 10}, + {MaxDuration: 30}, + }, + }, + wantDuration: 10, + }, + { + name: "unsorted_array_round_to_minimum", + args: args{ + duration: 5, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 40}, + {MaxDuration: 20}, + {MaxDuration: 10}, + {MaxDuration: 30}, + }, + }, + wantDuration: 10, + }, + { + name: "unsorted_array_invalid", + args: args{ + duration: 45, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 40}, + {MaxDuration: 20}, + {MaxDuration: 10}, + {MaxDuration: 30}, + }, + }, + wantDuration: -1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + duration := GetNearestDuration(tt.args.duration, tt.args.config) + assert.Equal(t, tt.wantDuration, duration) + }) + } +} + +func TestErrToBidderMessage(t *testing.T) { + type args struct { + err error + } + tests := []struct { + name string + args args + want *openrtb_ext.ExtBidderMessage + }{ + { + name: `nil_check`, + args: args{err: nil}, + want: nil, + }, + { + name: `normal_error`, + args: args{err: fmt.Errorf(`normal_error`)}, + want: &openrtb_ext.ExtBidderMessage{ + Code: errortypes.UnknownErrorCode, + Message: `normal_error`, + }, + }, + { + name: `prebid_ctv_error`, + args: args{err: &errortypes.Timeout{Message: `timeout`}}, + want: &openrtb_ext.ExtBidderMessage{ + Code: errortypes.TimeoutErrorCode, + Message: `timeout`, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ErrToBidderMessage(tt.args.err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/endpoints/openrtb2/ctv_auction.go b/endpoints/openrtb2/ctv_auction.go new file mode 100644 index 00000000000..fa84918de92 --- /dev/null +++ b/endpoints/openrtb2/ctv_auction.go @@ -0,0 +1,1254 @@ +package openrtb2 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "time" + + "github.com/beevik/etree" + "github.com/buger/jsonparser" + uuid "github.com/gofrs/uuid" + "github.com/golang/glog" + "github.com/julienschmidt/httprouter" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/endpoints/events" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/combination" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/constant" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/impressions" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/response" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/uuidutil" +) + +// CTV Specific Endpoint +type ctvEndpointDeps struct { + endpointDeps + request *openrtb2.BidRequest + reqExt *openrtb_ext.ExtRequestAdPod + impData []*types.ImpData + videoSeats []*openrtb2.SeatBid //stores pure video impression bids + impIndices map[string]int + isAdPodRequest bool + impsExtPrebidBidder map[string]map[string]map[string]interface{} + impPartnerBlockedTagIDMap map[string]map[string][]string + + labels metrics.Labels +} + +// NewCTVEndpoint new ctv endpoint object +func NewCTVEndpoint( + ex exchange.Exchange, + validator openrtb_ext.BidderParamValidator, + requestsByID stored_requests.Fetcher, + videoFetcher stored_requests.Fetcher, + accounts stored_requests.AccountFetcher, + //categories stored_requests.CategoryFetcher, + cfg *config.Configuration, + met metrics.MetricsEngine, + analyticsRunner analytics.Runner, + disabledBidders map[string]string, + defReqJSON []byte, + bidderMap map[string]openrtb_ext.BidderName, + planBuilder hooks.ExecutionPlanBuilder, + tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed) (httprouter.Handle, error) { + + if ex == nil || validator == nil || requestsByID == nil || accounts == nil || cfg == nil || met == nil { + return nil, errors.New("NewCTVEndpoint requires non-nil arguments") + } + defRequest := len(defReqJSON) > 0 + + ipValidator := iputil.PublicNetworkIPValidator{ + IPv4PrivateNetworks: cfg.RequestValidation.IPv4PrivateNetworksParsed, + IPv6PrivateNetworks: cfg.RequestValidation.IPv6PrivateNetworksParsed, + } + var uuidGenerator uuidutil.UUIDGenerator + return httprouter.Handle((&ctvEndpointDeps{ + endpointDeps: endpointDeps{ + uuidGenerator, + ex, + validator, + requestsByID, + videoFetcher, + accounts, + cfg, + met, + analyticsRunner, + disabledBidders, + defRequest, + defReqJSON, + bidderMap, + nil, + nil, + ipValidator, + nil, + planBuilder, + tmaxAdjustments, + openrtb_ext.NormalizeBidderName, + }, + }).CTVAuctionEndpoint), nil +} + +func (deps *ctvEndpointDeps) CTVAuctionEndpoint(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + defer util.TimeTrack(time.Now(), "CTVAuctionEndpoint") + + var reqWrapper *openrtb_ext.RequestWrapper + var request *openrtb2.BidRequest + var response *openrtb2.BidResponse + var err error + var errL []error + + ao := analytics.AuctionObject{ + Status: http.StatusOK, + Errors: make([]error, 0), + } + activityControl := privacy.ActivityControl{} + + // Prebid Server interprets request.tmax to be the maximum amount of time that a caller is willing + // to wait for bids. However, tmax may be defined in the Stored Request data. + // + // If so, then the trip to the backend might use a significant amount of this time. + // We can respect timeouts more accurately if we note the *real* start time, and use it + // to compute the auction timeout. + start := time.Now() + //Prebid Stats + deps.labels = metrics.Labels{ + Source: metrics.DemandUnknown, + RType: metrics.ReqTypeVideo, + PubID: metrics.PublisherUnknown, + CookieFlag: metrics.CookieFlagUnknown, + RequestStatus: metrics.RequestStatusOK, + } + defer func() { + deps.metricsEngine.RecordRequest(deps.labels) + recordRejectedBids(deps.labels.PubID, ao.SeatNonBid, deps.metricsEngine) + deps.metricsEngine.RecordRequestTime(deps.labels, time.Since(start)) + deps.analytics.LogAuctionObject(&ao, activityControl) + }() + + hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointCtv, deps.metricsEngine) + + //Parse ORTB Request and do Standard Validation + reqWrapper, _, _, _, _, _, errL = deps.parseRequest(r, &deps.labels, hookExecutor) + if errortypes.ContainsFatalError(errL) && writeError(errL, w, &deps.labels) { + return + } + if reqWrapper.RebuildRequestExt() != nil { + return + } + request = reqWrapper.BidRequest + + util.JLogf("Original BidRequest", request) //TODO: REMOVE LOG + + //init + deps.init(request) + + //Set Default Values + deps.setDefaultValues() + util.JLogf("Extensions Request Extension", deps.reqExt) + util.JLogf("Extensions ImpData", deps.impData) + + //Validate CTV BidRequest + if err := deps.validateBidRequest(); err != nil { + errL = append(errL, err...) + writeError(errL, w, &deps.labels) + return + } + + if deps.isAdPodRequest { + //Create New BidRequest + request = deps.createBidRequest(request) + util.JLogf("CTV BidRequest", request) //TODO: REMOVE LOG + } + + //Parsing Cookies and Set Stats + usersyncs := usersync.ReadCookie(r, usersync.Base64Decoder{}, &deps.cfg.HostCookie) + usersync.SyncHostCookie(r, usersyncs, &deps.cfg.HostCookie) + + if request.App != nil { + deps.labels.Source = metrics.DemandApp + deps.labels.RType = metrics.ReqTypeVideo + deps.labels.PubID = getAccountID(request.App.Publisher) + } else { //request.Site != nil + deps.labels.Source = metrics.DemandWeb + if !usersyncs.HasAnyLiveSyncs() { + deps.labels.CookieFlag = metrics.CookieFlagNo + } else { + deps.labels.CookieFlag = metrics.CookieFlagYes + } + deps.labels.PubID = getAccountID(request.Site.Publisher) + } + ctx := r.Context() + + // Look up account now that we have resolved the pubID value + account, acctIDErrs := accountService.GetAccount(ctx, deps.cfg, deps.accounts, deps.labels.PubID, deps.metricsEngine) + if len(acctIDErrs) > 0 { + errL = append(errL, acctIDErrs...) + writeError(errL, w, &deps.labels) + return + } + + //Setting Timeout for Request + timeout := deps.cfg.AuctionTimeouts.LimitAuctionTimeout(time.Duration(request.TMax) * time.Millisecond) + if timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithDeadline(ctx, start.Add(timeout)) + defer cancel() + } + + tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR) + reqWrapper.BidRequest = request + auctionRequest := exchange.AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: request}, + Account: *account, + UserSyncs: usersyncs, + RequestType: deps.labels.RType, + StartTime: start, + LegacyLabels: deps.labels, + PubID: deps.labels.PubID, + HookExecutor: hookExecutor, + TCF2Config: tcf2Config, + TmaxAdjustments: deps.tmaxAdjustments, + } + + auctionResponse, err := deps.holdAuction(ctx, auctionRequest) + defer func() { + if !auctionRequest.BidderResponseStartTime.IsZero() { + deps.metricsEngine.RecordOverheadTime(metrics.MakeAuctionResponse, time.Since(auctionRequest.BidderResponseStartTime)) + } + }() + + ao.RequestWrapper = auctionRequest.BidRequestWrapper + if err != nil || auctionResponse == nil || auctionResponse.BidResponse == nil { + deps.labels.RequestStatus = metrics.RequestStatusErr + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Critical error while running the auction: %v", err) + glog.Errorf("/openrtb2/video Critical error: %v", err) + ao.Status = http.StatusInternalServerError + ao.Errors = append(ao.Errors, err) + return + } + ao.SeatNonBid = auctionResponse.GetSeatNonBid() + err = setSeatNonBidRaw(ao.RequestWrapper, auctionResponse) + if err != nil { + glog.Errorf("Error setting seat non-bid: %v", err) + } + response = auctionResponse.BidResponse + util.JLogf("BidResponse", response) //TODO: REMOVE LOG + + if deps.isAdPodRequest { + //Validate Bid Response + if err := deps.validateBidResponse(request, response); err != nil { + errL = append(errL, err) + writeError(errL, w, &deps.labels) + return + } + + //Create Impression Bids + deps.getBids(response) + + //Do AdPod Exclusions + bids := deps.doAdPodExclusions() + + //Create Bid Response + adPodBidResponse := deps.createAdPodBidResponse(response, bids) + + //Set bid.Ext params - adpod.aprc, prebid.video.duration + deps.setBidExtParams() + + deps.recordRejectedAdPodBids(deps.labels.PubID) + adPodBidResponse.Ext = deps.getBidResponseExt(response) + response = adPodBidResponse + + util.JLogf("CTV BidResponse", response) //TODO: REMOVE LOG + } + ao.Response = response + + // Response Generation + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + + // Fixes #328 + w.Header().Set("Content-Type", "application/json") + + // If an error happens when encoding the response, there isn't much we can do. + // If we've sent _any_ bytes, then Go would have sent the 200 status code first. + // That status code can't be un-sent... so the best we can do is log the error. + if err := enc.Encode(response); err != nil { + deps.labels.RequestStatus = metrics.RequestStatusNetworkErr + ao.Errors = append(ao.Errors, fmt.Errorf("/openrtb2/video Failed to send response: %v", err)) + } +} + +func (deps *ctvEndpointDeps) holdAuction(ctx context.Context, auctionRequest exchange.AuctionRequest) (*exchange.AuctionResponse, error) { + defer util.TimeTrack(time.Now(), fmt.Sprintf("Tid:%v CTVHoldAuction", deps.request.ID)) + + //Hold OpenRTB Standard Auction + if len(deps.request.Imp) == 0 { + //Dummy Response Object + return &exchange.AuctionResponse{BidResponse: &openrtb2.BidResponse{ID: deps.request.ID}}, nil + } + + return deps.ex.HoldAuction(ctx, &auctionRequest, nil) +} + +/********************* BidRequest Processing *********************/ + +func (deps *ctvEndpointDeps) init(req *openrtb2.BidRequest) { + deps.request = req + deps.impData = make([]*types.ImpData, len(req.Imp)) + deps.impIndices = make(map[string]int, len(req.Imp)) + + for i := range req.Imp { + deps.impIndices[req.Imp[i].ID] = i + deps.impData[i] = &types.ImpData{} + } +} + +func (deps *ctvEndpointDeps) readVideoAdPodExt() (err []error) { + for index, imp := range deps.request.Imp { + if nil != imp.Video { + vidExt := openrtb_ext.ExtVideoAdPod{} + if len(imp.Video.Ext) > 0 { + errL := json.Unmarshal(imp.Video.Ext, &vidExt) + if nil != err { + err = append(err, errL) + continue + } + + imp.Video.Ext = jsonparser.Delete(imp.Video.Ext, constant.CTVAdpod) + imp.Video.Ext = jsonparser.Delete(imp.Video.Ext, constant.CTVOffset) + if string(imp.Video.Ext) == `{}` { + imp.Video.Ext = nil + } + } + + if nil == vidExt.AdPod { + if nil == deps.reqExt { + continue + } + vidExt.AdPod = &openrtb_ext.VideoAdPod{} + } + + //Use Request Level Parameters + if nil != deps.reqExt { + vidExt.AdPod.Merge(&deps.reqExt.VideoAdPod) + } + + //Set Default Values + vidExt.SetDefaultValue() + vidExt.AdPod.SetDefaultAdDurations(imp.Video.MinDuration, imp.Video.MaxDuration) + + deps.impData[index].VideoExt = &vidExt + } + } + return err +} + +func (deps *ctvEndpointDeps) readRequestExtension() (err []error) { + if len(deps.request.Ext) > 0 { + + //TODO: use jsonparser library for get adpod and remove that key + extAdPod, jsonType, _, errL := jsonparser.Get(deps.request.Ext, constant.CTVAdpod) + + if nil != errL { + //parsing error + if jsonparser.NotExist != jsonType { + //assuming key not present + err = append(err, errL) + return + } + } else { + deps.reqExt = &openrtb_ext.ExtRequestAdPod{} + + if errL := json.Unmarshal(extAdPod, deps.reqExt); nil != errL { + err = append(err, errL) + return + } + + deps.reqExt.SetDefaultValue() + } + } + + return +} + +func (deps *ctvEndpointDeps) readExtensions() (err []error) { + if errL := deps.readRequestExtension(); nil != errL { + err = append(err, errL...) + } + + if errL := deps.readVideoAdPodExt(); nil != errL { + err = append(err, errL...) + } + return err +} + +func (deps *ctvEndpointDeps) setIsAdPodRequest() { + deps.isAdPodRequest = false + for _, data := range deps.impData { + if nil != data.VideoExt && nil != data.VideoExt.AdPod { + deps.isAdPodRequest = true + break + } + } +} + +// setDefaultValues will set adpod and other default values +func (deps *ctvEndpointDeps) setDefaultValues() { + //read and set extension values + deps.readExtensions() + + //set request is adpod request or normal request + deps.setIsAdPodRequest() + + if deps.isAdPodRequest { + deps.readImpExtensionsAndTags() + } +} + +// validateBidRequest will validate AdPod specific mandatory Parameters and returns error +func (deps *ctvEndpointDeps) validateBidRequest() (err []error) { + //validating video extension adpod configurations + if nil != deps.reqExt { + err = deps.reqExt.Validate() + } + + for index, imp := range deps.request.Imp { + if nil != imp.Video && nil != deps.impData[index].VideoExt { + ext := deps.impData[index].VideoExt + if errL := ext.Validate(); nil != errL { + err = append(err, errL...) + } + + if nil != ext.AdPod { + if errL := ext.AdPod.ValidateAdPodDurations(imp.Video.MinDuration, imp.Video.MaxDuration, imp.Video.MaxExtended); nil != errL { + err = append(err, errL...) + } + } + } + + } + return +} + +// readImpExtensionsAndTags will read the impression extensions +func (deps *ctvEndpointDeps) readImpExtensionsAndTags() (errs []error) { + deps.impsExtPrebidBidder = make(map[string]map[string]map[string]interface{}) + deps.impPartnerBlockedTagIDMap = make(map[string]map[string][]string) //Initially this will have all tags, eligible tags will be filtered in filterImpsVastTagsByDuration + + for _, imp := range deps.request.Imp { + bidderExtBytes, _, _, err := jsonparser.Get(imp.Ext, "prebid", "bidder") + if err != nil { + errs = append(errs, err) + continue + } + impsExtPrebidBidder := make(map[string]map[string]interface{}) + + err = json.Unmarshal(bidderExtBytes, &impsExtPrebidBidder) + if err != nil { + errs = append(errs, err) + continue + } + + deps.impPartnerBlockedTagIDMap[imp.ID] = make(map[string][]string) + + for partnerName, partnerExt := range impsExtPrebidBidder { + impVastTags, ok := partnerExt["tags"].([]interface{}) + if !ok { + continue + } + + for _, tag := range impVastTags { + vastTag, ok := tag.(map[string]interface{}) + if !ok { + continue + } + + deps.impPartnerBlockedTagIDMap[imp.ID][partnerName] = append(deps.impPartnerBlockedTagIDMap[imp.ID][partnerName], vastTag["tagid"].(string)) + } + } + + deps.impsExtPrebidBidder[imp.ID] = impsExtPrebidBidder + } + + return errs +} + +/********************* Creating CTV BidRequest *********************/ + +// createBidRequest will return new bid request with all things copy from bid request except impression objects +func (deps *ctvEndpointDeps) createBidRequest(req *openrtb2.BidRequest) *openrtb2.BidRequest { + ctvRequest := *req + + //get configurations for all impressions + deps.getAllAdPodImpsConfigs() + + //createImpressions + ctvRequest.Imp = deps.createImpressions() + + deps.filterImpsVastTagsByDuration(&ctvRequest) + + //TODO: remove adpod extension if not required to send further + return &ctvRequest +} + +// filterImpsVastTagsByDuration checks if a Vast tag should be called for a generated impression based on the duration of tag and impression +func (deps *ctvEndpointDeps) filterImpsVastTagsByDuration(bidReq *openrtb2.BidRequest) { + + for impCount, imp := range bidReq.Imp { + index := strings.LastIndex(imp.ID, "_") + if index == -1 { + continue + } + + originalImpID := imp.ID[:index] + + impExtBidder := deps.impsExtPrebidBidder[originalImpID] + impExtBidderCopy := make(map[string]map[string]interface{}) + for partnerName, partnerExt := range impExtBidder { + impExtBidderCopy[partnerName] = partnerExt + } + + for partnerName, partnerExt := range impExtBidderCopy { + if partnerExt["tags"] != nil { + impVastTags, ok := partnerExt["tags"].([]interface{}) + if !ok { + continue + } + + var compatibleVasts []interface{} + for _, tag := range impVastTags { + vastTag, ok := tag.(map[string]interface{}) + if !ok { + continue + } + + tagDuration := int(vastTag["dur"].(float64)) + if int(imp.Video.MinDuration) <= tagDuration && tagDuration <= int(imp.Video.MaxDuration) { + compatibleVasts = append(compatibleVasts, tag) + + deps.impPartnerBlockedTagIDMap[originalImpID][partnerName] = remove(deps.impPartnerBlockedTagIDMap[originalImpID][partnerName], vastTag["tagid"].(string)) + if len(deps.impPartnerBlockedTagIDMap[originalImpID][partnerName]) == 0 { + delete(deps.impPartnerBlockedTagIDMap[originalImpID], partnerName) + } + } + } + + if len(compatibleVasts) < 1 { + delete(impExtBidderCopy, partnerName) + } else { + impExtBidderCopy[partnerName] = map[string]interface{}{ + "tags": compatibleVasts, + } + } + } + } + + bidderExtBytes, err := json.Marshal(impExtBidderCopy) + if err != nil { + continue + } + + // if imp.ext exists then set prebid.bidder inside it + impExt, err := jsonparser.Set(imp.Ext, bidderExtBytes, "prebid", "bidder") + if err != nil { + continue + } + + imp.Ext = impExt + bidReq.Imp[impCount] = imp + } + + for impID, blockedTags := range deps.impPartnerBlockedTagIDMap { + for _, datum := range deps.impData { + if datum.ImpID == impID { + datum.BlockedVASTTags = blockedTags + break + } + } + } +} + +func remove(slice []string, item string) []string { + index := -1 + for i := range slice { + if slice[i] == item { + index = i + break + } + } + + if index == -1 { + return slice + } + + return append(slice[:index], slice[index+1:]...) +} + +// getAllAdPodImpsConfigs will return all impression adpod configurations +func (deps *ctvEndpointDeps) getAllAdPodImpsConfigs() { + for index, imp := range deps.request.Imp { + if nil == imp.Video || nil == deps.impData[index].VideoExt || nil == deps.impData[index].VideoExt.AdPod { + continue + } + deps.impData[index].ImpID = imp.ID + + config, err := deps.getAdPodImpsConfigs(&imp, deps.impData[index].VideoExt.AdPod) + if err != nil { + deps.impData[index].Error = util.ErrToBidderMessage(err) + continue + } + deps.impData[index].Config = config[:] + } +} + +// getAdPodImpsConfigs will return number of impressions configurations within adpod +func (deps *ctvEndpointDeps) getAdPodImpsConfigs(imp *openrtb2.Imp, adpod *openrtb_ext.VideoAdPod) ([]*types.ImpAdPodConfig, error) { + // monitor + start := time.Now() + selectedAlgorithm := impressions.SelectAlgorithm(deps.reqExt) + impGen := impressions.NewImpressions(imp.Video.MinDuration, imp.Video.MaxDuration, deps.reqExt, adpod, selectedAlgorithm) + impRanges := impGen.Get() + labels := metrics.PodLabels{AlgorithmName: impressions.MonitorKey[selectedAlgorithm], NoOfImpressions: new(int)} + + //log number of impressions in stats + *labels.NoOfImpressions = len(impRanges) + deps.metricsEngine.RecordPodImpGenTime(labels, start) + + // check if algorithm has generated impressions + if len(impRanges) == 0 { + return nil, util.UnableToGenerateImpressionsError + } + + config := make([]*types.ImpAdPodConfig, len(impRanges)) + for i, value := range impRanges { + config[i] = &types.ImpAdPodConfig{ + ImpID: util.GetCTVImpressionID(imp.ID, i+1), + MinDuration: value[0], + MaxDuration: value[1], + SequenceNumber: int8(i + 1), /* Must be starting with 1 */ + } + } + return config[:], nil +} + +// createImpressions will create multiple impressions based on adpod configurations +func (deps *ctvEndpointDeps) createImpressions() []openrtb2.Imp { + impCount := 0 + for _, imp := range deps.impData { + if nil == imp.Error { + if len(imp.Config) == 0 { + impCount = impCount + 1 + } else { + impCount = impCount + len(imp.Config) + } + } + } + + count := 0 + imps := make([]openrtb2.Imp, impCount) + for index, imp := range deps.request.Imp { + if nil == deps.impData[index].Error { + adPodConfig := deps.impData[index].Config + if len(adPodConfig) == 0 { + //non adpod request it will be normal video impression + imps[count] = imp + count++ + } else { + //for adpod request it will create new impression based on configurations + for _, config := range adPodConfig { + imps[count] = *(newImpression(&imp, config)) + count++ + } + } + } + } + return imps[:] +} + +// newImpression will clone existing impression object and create video object with ImpAdPodConfig. +func newImpression(imp *openrtb2.Imp, config *types.ImpAdPodConfig) *openrtb2.Imp { + video := *imp.Video + video.MinDuration = config.MinDuration + video.MaxDuration = config.MaxDuration + video.Sequence = config.SequenceNumber + video.MaxExtended = 0 + //TODO: remove video adpod extension if not required + + newImp := *imp + newImp.ID = config.ImpID + //newImp.BidFloor = 0 + newImp.Video = &video + return &newImp +} + +/********************* Prebid BidResponse Processing *********************/ + +// validateBidResponse +func (deps *ctvEndpointDeps) validateBidResponse(req *openrtb2.BidRequest, resp *openrtb2.BidResponse) error { + //remove bids withoug cat and adomain + + return nil +} + +// getBids reads bids from bidresponse object +func (deps *ctvEndpointDeps) getBids(resp *openrtb2.BidResponse) { + var vseat *openrtb2.SeatBid + result := make(map[string]*types.AdPodBid) + + for i := range resp.SeatBid { + seat := resp.SeatBid[i] + vseat = nil + + for j := range seat.Bid { + bid := &seat.Bid[j] + + if len(bid.ID) == 0 { + bidID, err := uuid.NewV4() + if nil != err { + continue + } + bid.ID = bidID.String() + } + + if bid.Price == 0 { + //filter invalid bids + continue + } + + originalImpID, sequenceNumber := deps.getImpressionID(bid.ImpID) + if sequenceNumber < 0 { + continue + } + + value, err := util.GetTargeting(openrtb_ext.HbCategoryDurationKey, openrtb_ext.BidderName(seat.Seat), *bid) + if nil == err { + // ignore error + addTargetingKey(bid, openrtb_ext.HbCategoryDurationKey, value) + } + + value, err = util.GetTargeting(openrtb_ext.HbpbConstantKey, openrtb_ext.BidderName(seat.Seat), *bid) + if nil == err { + // ignore error + addTargetingKey(bid, openrtb_ext.HbpbConstantKey, value) + } + + index := deps.impIndices[originalImpID] + if len(deps.impData[index].Config) == 0 { + //adding pure video bids + if vseat == nil { + vseat = &openrtb2.SeatBid{ + Seat: seat.Seat, + Group: seat.Group, + Ext: seat.Ext, + } + deps.videoSeats = append(deps.videoSeats, vseat) + } + vseat.Bid = append(vseat.Bid, *bid) + } else { + //reading extension, ingorning parsing error + ext := openrtb_ext.ExtBid{} + if nil != bid.Ext { + json.Unmarshal(bid.Ext, &ext) + } + + //Adding adpod bids + impBids, ok := result[originalImpID] + if !ok { + impBids = &types.AdPodBid{ + OriginalImpID: originalImpID, + SeatName: string(openrtb_ext.BidderOWPrebidCTV), + } + result[originalImpID] = impBids + } + + if deps.cfg.GenerateBidID == false { + //making unique bid.id's per impression + bid.ID = util.GetUniqueBidID(bid.ID, len(impBids.Bids)+1) + } + + //get duration of creative + duration, status := getBidDuration(bid, deps.reqExt, deps.impData[index].Config, + deps.impData[index].Config[sequenceNumber-1].MaxDuration) + + impBids.Bids = append(impBids.Bids, &types.Bid{ + Bid: bid, + ExtBid: ext, + Status: status, + Duration: int(duration), + DealTierSatisfied: util.GetDealTierSatisfied(&ext), + Seat: seat.Seat, + }) + } + } + } + + //Sort Bids by Price + for index, imp := range deps.request.Imp { + impBids, ok := result[imp.ID] + if ok { + //sort bids + sort.Slice(impBids.Bids[:], func(i, j int) bool { return impBids.Bids[i].Price > impBids.Bids[j].Price }) + deps.impData[index].Bid = impBids + } + } +} + +// getImpressionID will return impression id and sequence number +func (deps *ctvEndpointDeps) getImpressionID(id string) (string, int) { + //get original impression id and sequence number + originalImpID, sequenceNumber := util.DecodeImpressionID(id) + + //check originalImpID present in request or not + index, ok := deps.impIndices[originalImpID] + if !ok { + //if not present check impression id present in request or not + _, ok = deps.impIndices[id] + if !ok { + return id, -1 + } + return originalImpID, 0 + } + + if sequenceNumber < 0 || sequenceNumber > len(deps.impData[index].Config) { + return id, -1 + } + + return originalImpID, sequenceNumber +} + +// doAdPodExclusions +func (deps *ctvEndpointDeps) doAdPodExclusions() types.AdPodBids { + defer util.TimeTrack(time.Now(), fmt.Sprintf("Tid:%v doAdPodExclusions", deps.request.ID)) + + result := types.AdPodBids{} + for index := 0; index < len(deps.request.Imp); index++ { + bid := deps.impData[index].Bid + if nil != bid && len(bid.Bids) > 0 { + //TODO: MULTI ADPOD IMPRESSIONS + //duration wise buckets sorted + buckets := util.GetDurationWiseBidsBucket(bid.Bids[:]) + + if len(buckets) == 0 { + deps.impData[index].Error = util.DurationMismatchWarning + continue + } + + //combination generator + comb := combination.NewCombination( + buckets, + uint64(deps.request.Imp[index].Video.MinDuration), + uint64(deps.request.Imp[index].Video.MaxDuration), + deps.impData[index].VideoExt.AdPod) + + //adpod generator + adpodGenerator := response.NewAdPodGenerator(deps.request, index, buckets, comb, deps.impData[index].VideoExt.AdPod, deps.metricsEngine) + + adpodBids := adpodGenerator.GetAdPodBids() + if adpodBids == nil { + deps.impData[index].Error = util.UnableToGenerateAdPodWarning + continue + } + + adpodBids.OriginalImpID = bid.OriginalImpID + adpodBids.SeatName = bid.SeatName + result = append(result, adpodBids) + } + } + return result +} + +/********************* Creating CTV BidResponse *********************/ + +// createAdPodBidResponse +func (deps *ctvEndpointDeps) createAdPodBidResponse(resp *openrtb2.BidResponse, adpods types.AdPodBids) *openrtb2.BidResponse { + defer util.TimeTrack(time.Now(), fmt.Sprintf("Tid:%v createAdPodBidResponse", deps.request.ID)) + + bidResp := &openrtb2.BidResponse{ + ID: resp.ID, + Cur: resp.Cur, + CustomData: resp.CustomData, + SeatBid: deps.getBidResponseSeatBids(adpods), + } + return bidResp +} + +func (deps *ctvEndpointDeps) getBidResponseSeatBids(adpods types.AdPodBids) []openrtb2.SeatBid { + seats := []openrtb2.SeatBid{} + + //append pure video request seats + for _, seat := range deps.videoSeats { + seats = append(seats, *seat) + } + + var adpodSeat *openrtb2.SeatBid + for _, adpod := range adpods { + if len(adpod.Bids) == 0 { + continue + } + + bid := deps.getAdPodBid(adpod) + if bid != nil { + if nil == adpodSeat { + adpodSeat = &openrtb2.SeatBid{ + Seat: adpod.SeatName, + } + } + adpodSeat.Bid = append(adpodSeat.Bid, *bid.Bid) + } + } + if nil != adpodSeat { + seats = append(seats, *adpodSeat) + } + return seats[:] +} + +// getAdPodBid +func (deps *ctvEndpointDeps) getAdPodBid(adpod *types.AdPodBid) *types.Bid { + bid := types.Bid{ + Bid: &openrtb2.Bid{}, + } + + //TODO: Write single for loop to get all details + bidID, err := uuid.NewV4() + if nil == err { + bid.ID = bidID.String() + } else { + bid.ID = adpod.Bids[0].ID + } + + bid.ImpID = adpod.OriginalImpID + bid.Price = adpod.Price + bid.ADomain = adpod.ADomain[:] + bid.Cat = adpod.Cat[:] + bid.AdM = *getAdPodBidCreative(deps.request.Imp[deps.impIndices[adpod.OriginalImpID]].Video, adpod, deps.cfg.GenerateBidID) + bid.Ext = getAdPodBidExtension(adpod) + return &bid +} + +// getAdPodBidCreative get commulative adpod bid details +func getAdPodBidCreative(video *openrtb2.Video, adpod *types.AdPodBid, generatedBidID bool) *string { + doc := etree.NewDocument() + vast := doc.CreateElement(constant.VASTElement) + sequenceNumber := 1 + var version float64 = 2.0 + + for _, bid := range adpod.Bids { + var newAd *etree.Element + + if strings.HasPrefix(bid.AdM, constant.HTTPPrefix) { + newAd = etree.NewElement(constant.VASTAdElement) + wrapper := newAd.CreateElement(constant.VASTWrapperElement) + vastAdTagURI := wrapper.CreateElement(constant.VASTAdTagURIElement) + vastAdTagURI.CreateCharData(bid.AdM) + } else { + adDoc := etree.NewDocument() + if err := adDoc.ReadFromString(bid.AdM); err != nil { + continue + } + + if generatedBidID == false { + // adjust bidid in video event trackers and update + adjustBidIDInVideoEventTrackers(adDoc, bid.Bid) + adm, err := adDoc.WriteToString() + if nil != err { + util.JLogf("ERROR, %v", err.Error()) + } else { + bid.AdM = adm + } + } + + vastTag := adDoc.SelectElement(constant.VASTElement) + + //Get Actual VAST Version + bidVASTVersion, _ := strconv.ParseFloat(vastTag.SelectAttrValue(constant.VASTVersionAttribute, constant.VASTDefaultVersionStr), 64) + version = math.Max(version, bidVASTVersion) + + ads := vastTag.SelectElements(constant.VASTAdElement) + if len(ads) > 0 { + newAd = ads[0].Copy() + } + } + + if nil != newAd { + //creative.AdId attribute needs to be updated + newAd.CreateAttr(constant.VASTSequenceAttribute, fmt.Sprint(sequenceNumber)) + vast.AddChild(newAd) + sequenceNumber++ + } + } + + if int(version) > len(constant.VASTVersionsStr) { + version = constant.VASTMaxVersion + } + + vast.CreateAttr(constant.VASTVersionAttribute, constant.VASTVersionsStr[int(version)]) + bidAdM, err := doc.WriteToString() + if nil != err { + fmt.Printf("ERROR, %v", err.Error()) + return nil + } + return &bidAdM +} + +// getAdPodBidExtension get commulative adpod bid details +func getAdPodBidExtension(adpod *types.AdPodBid) json.RawMessage { + bidExt := &openrtb_ext.ExtOWBid{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Type: openrtb_ext.BidTypeVideo, + Video: &openrtb_ext.ExtBidPrebidVideo{}, + }, + }, + AdPod: &openrtb_ext.BidAdPodExt{ + RefBids: make([]string, len(adpod.Bids)), + }, + } + + for i, bid := range adpod.Bids { + //get unique bid id + bidID := bid.ID + if bid.ExtBid.Prebid != nil && bid.ExtBid.Prebid.BidId != "" { + bidID = bid.ExtBid.Prebid.BidId + } + + //adding bid id in adpod.refbids + bidExt.AdPod.RefBids[i] = bidID + + //updating exact duration of adpod creative + bidExt.Prebid.Video.Duration += int(bid.Duration) + + //setting bid status as winning bid + bid.Status = constant.StatusWinningBid + } + rawExt, _ := json.Marshal(bidExt) + return rawExt +} + +// getDurationBasedOnDurationMatchingPolicy will return duration based on durationmatching policy +func getDurationBasedOnDurationMatchingPolicy(duration int64, policy openrtb_ext.OWVideoAdDurationMatchingPolicy, config []*types.ImpAdPodConfig) (int64, constant.BidStatus) { + switch policy { + case openrtb_ext.OWExactVideoAdDurationMatching: + tmp := util.GetNearestDuration(duration, config) + if tmp != duration { + return duration, constant.StatusDurationMismatch + } + //its and valid duration return it with StatusOK + + case openrtb_ext.OWRoundupVideoAdDurationMatching: + tmp := util.GetNearestDuration(duration, config) + if tmp == -1 { + return duration, constant.StatusDurationMismatch + } + //update duration with nearest one duration + duration = tmp + //its and valid duration return it with StatusOK + } + + return duration, constant.StatusOK +} + +/* +getBidDuration determines the duration of video ad from given bid. +it will try to get the actual ad duration returned by the bidder using prebid.video.duration +if prebid.video.duration not present then uses defaultDuration passed as an argument +if video lengths matching policy is present for request then it will validate and update duration based on policy +*/ +func getBidDuration(bid *openrtb2.Bid, reqExt *openrtb_ext.ExtRequestAdPod, config []*types.ImpAdPodConfig, defaultDuration int64) (int64, constant.BidStatus) { + + // C1: Read it from bid.ext.prebid.video.duration field + duration, err := jsonparser.GetInt(bid.Ext, "prebid", "video", "duration") + if nil != err || duration <= 0 { + // incase if duration is not present use impression duration directly as it is + return defaultDuration, constant.StatusOK + } + + // C2: Based on video lengths matching policy validate and return duration + if nil != reqExt && len(reqExt.VideoAdDurationMatching) > 0 { + return getDurationBasedOnDurationMatchingPolicy(duration, reqExt.VideoAdDurationMatching, config) + } + + //default return duration which is present in bid.ext.prebid.vide.duration field + return duration, constant.StatusOK +} + +func addTargetingKey(bid *openrtb2.Bid, key openrtb_ext.TargetingKey, value string) error { + if nil == bid { + return errors.New("Invalid bid") + } + + raw, err := jsonparser.Set(bid.Ext, []byte(strconv.Quote(value)), "prebid", "targeting", string(key)) + if nil == err { + bid.Ext = raw + } + return err +} + +func adjustBidIDInVideoEventTrackers(doc *etree.Document, bid *openrtb2.Bid) { + // adjusment: update bid.id with ctv module generated bid.id + creatives := events.FindCreatives(doc) + for _, creative := range creatives { + trackingEvents := creative.FindElements("TrackingEvents/Tracking") + if nil != trackingEvents { + // update bidid= value with ctv generated bid id for this bid + for _, trackingEvent := range trackingEvents { + u, e := url.Parse(trackingEvent.Text()) + if nil == e { + values, e := url.ParseQuery(u.RawQuery) + // only do replacment if operId=8 + if nil == e && nil != values["bidid"] && nil != values["operId"] && values["operId"][0] == "8" { + values.Set("bidid", bid.ID) + } else { + continue + } + + //OTT-183: Fix + if nil != values["operId"] && values["operId"][0] == "8" { + operID := values.Get("operId") + values.Del("operId") + values.Add("_operId", operID) // _ (underscore) will keep it as first key + } + + u.RawQuery = values.Encode() // encode sorts query params by key. _ must be first (assuing no other query param with _) + // replace _operId with operId + u.RawQuery = strings.ReplaceAll(u.RawQuery, "_operId", "operId") + trackingEvent.SetText(u.String()) + } + } + } + } +} + +// ConvertAPRCToNBRC converts the aprc to NonBidStatusCode +func ConvertAPRCToNBRC(bidStatus int64) *openrtb3.NonBidStatusCode { + var nbrCode openrtb3.NonBidStatusCode + + switch bidStatus { + case constant.StatusOK: + nbrCode = openrtb3.LossBidLostToHigherBid + case constant.StatusCategoryExclusion: + nbrCode = openrtb3.LossBidCategoryExclusions + case constant.StatusDomainExclusion: + nbrCode = openrtb3.LossBidAdvertiserExclusions + case constant.StatusDurationMismatch: + nbrCode = openrtb3.LossBidInvalidCreative + + default: + return nil + } + return &nbrCode +} + +// recordRejectedAdPodBids records the bids lost in ad-pod auction using metricsEngine +func (deps *ctvEndpointDeps) recordRejectedAdPodBids(pubID string) { + + for _, imp := range deps.impData { + if nil != imp.Bid && len(imp.Bid.Bids) > 0 { + for _, bid := range imp.Bid.Bids { + if bid.Status != constant.StatusWinningBid { + reason := ConvertAPRCToNBRC(bid.Status) + if reason == nil { + continue + } + rejReason := strconv.FormatInt(int64(*reason), 10) + deps.metricsEngine.RecordRejectedBids(pubID, bid.Seat, rejReason) + } + } + } + } +} + +// getBidResponseExt prepare and return the bidresponse extension +func (deps *ctvEndpointDeps) getBidResponseExt(resp *openrtb2.BidResponse) (data json.RawMessage) { + + var err error + + adpodExt := types.BidResponseAdPodExt{ + Response: *resp, + Config: make(map[string]*types.ImpData, len(deps.impData)), + } + + for index, imp := range deps.impData { + if nil != imp.VideoExt && nil != imp.VideoExt.AdPod { + adpodExt.Config[deps.request.Imp[index].ID] = imp + } + } + + //Remove extension parameter + adpodExt.Response.Ext = nil + + if resp.Ext == nil { + bidResponseExt := &types.ExtCTVBidResponse{ + AdPod: &adpodExt, + } + + data, err = json.Marshal(bidResponseExt) + if err != nil { + glog.Errorf("JSON Marshal Error: %v", err.Error()) + return nil + } + } else { + data, err = json.Marshal(adpodExt) + if err != nil { + glog.Errorf("JSON Marshal Error: %v", err.Error()) + return nil + } + + data, err = jsonparser.Set(resp.Ext, data, constant.CTVAdpod) + if err != nil { + glog.Errorf("JSONParser Set Error: %v", err.Error()) + return nil + } + } + return data[:] +} + +// setBidExtParams function sets the prebid.video.duration and adpod.aprc parameters +func (deps *ctvEndpointDeps) setBidExtParams() { + + for _, imp := range deps.impData { + if imp.Bid != nil { + for _, bid := range imp.Bid.Bids { + + //update adm + //bid.AdM = constant.VASTDefaultTag + + //add duration value + raw, err := jsonparser.Set(bid.Ext, []byte(strconv.Itoa(int(bid.Duration))), "prebid", "video", "duration") + if nil == err { + bid.Ext = raw + } + + //add bid filter reason value + raw, err = jsonparser.Set(bid.Ext, []byte(strconv.FormatInt(bid.Status, 10)), "adpod", "aprc") + if nil == err { + bid.Ext = raw + } + } + } + } +} diff --git a/endpoints/openrtb2/ctv_auction_test.go b/endpoints/openrtb2/ctv_auction_test.go new file mode 100644 index 00000000000..dd1f0c1d2c2 --- /dev/null +++ b/endpoints/openrtb2/ctv_auction_test.go @@ -0,0 +1,836 @@ +package openrtb2 + +import ( + "encoding/json" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/constant" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestAddTargetingKeys(t *testing.T) { + var tests = []struct { + scenario string // Testcase scenario + key string + value string + bidExt string + expect map[string]string + }{ + {scenario: "key_not_exists", key: "hb_pb_cat_dur", value: "some_value", bidExt: `{"prebid":{"targeting":{}}}`, expect: map[string]string{"hb_pb_cat_dur": "some_value"}}, + {scenario: "key_already_exists", key: "hb_pb_cat_dur", value: "new_value", bidExt: `{"prebid":{"targeting":{"hb_pb_cat_dur":"old_value"}}}`, expect: map[string]string{"hb_pb_cat_dur": "new_value"}}, + } + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + bid := new(openrtb2.Bid) + bid.Ext = []byte(test.bidExt) + key := openrtb_ext.TargetingKey(test.key) + assert.Nil(t, addTargetingKey(bid, key, test.value)) + extBid := openrtb_ext.ExtBid{} + json.Unmarshal(bid.Ext, &extBid) + assert.Equal(t, test.expect, extBid.Prebid.Targeting) + }) + } + assert.Equal(t, "Invalid bid", addTargetingKey(nil, openrtb_ext.HbCategoryDurationKey, "some value").Error()) +} + +func TestFilterImpsVastTagsByDuration(t *testing.T) { + type inputParams struct { + request *openrtb2.BidRequest + generatedRequest *openrtb2.BidRequest + impData []*types.ImpData + } + + type output struct { + reqs openrtb2.BidRequest + blockedTags []map[string][]string + } + + tt := []struct { + testName string + input inputParams + expectedOutput output + }{ + { + testName: "test_single_impression_single_vast_partner_with_no_excluded_tags", + input: inputParams{ + request: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1", Ext: []byte(`{"prebid": {"bidder": {"openx_vast_bidder": {"tags": [{"dur": 35,"tagid": "openx_35"}, {"dur": 25,"tagid": "openx_25"}, {"dur": 20,"tagid": "openx_20"}]}}}}`)}, + }, + }, + generatedRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{"prebid": {"bidder": {"openx_vast_bidder": {"tags": [{"dur": 35,"tagid": "openx_35"}, {"dur": 25,"tagid": "openx_25"}, {"dur": 20,"tagid": "openx_20"}]}}}}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"prebid": {"bidder": {"openx_vast_bidder": {"tags": [{"dur": 35,"tagid": "openx_35"}, {"dur": 25,"tagid": "openx_25"}, {"dur": 20,"tagid": "openx_20"}]}}}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 35}, Ext: []byte(`{"prebid": {"bidder": {"openx_vast_bidder": {"tags": [{"dur": 35,"tagid": "openx_35"}, {"dur": 25,"tagid": "openx_25"}, {"dur": 20,"tagid": "openx_20"}]}}}}`)}, + }, + }, + impData: []*types.ImpData{}, + }, + expectedOutput: output{ + reqs: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{"prebid": {"bidder": {}}}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"prebid": {"bidder": {"openx_vast_bidder":{"tags":[{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 35}, Ext: []byte(`{"prebid": {"bidder": {"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"}]}}}}`)}, + }, + }, + blockedTags: []map[string][]string{}, + }, + }, + { + testName: "test_single_impression_single_vast_partner_with_excluded_tags", + input: inputParams{ + request: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1", Ext: []byte(`{"prebid": {"bidder": {"openx_vast_bidder": {"tags": [{"dur": 35,"tagid": "openx_35"}, {"dur": 25,"tagid": "openx_25"}, {"dur": 20,"tagid": "openx_20"}]}}}}`)}, + }, + }, + generatedRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{"prebid": {"bidder": {"openx_vast_bidder": {"tags": [{"dur": 35,"tagid": "openx_35"}, {"dur": 25,"tagid": "openx_25"}, {"dur": 20,"tagid": "openx_20"}]}}}}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"prebid": {"bidder": {"openx_vast_bidder": {"tags": [{"dur": 35,"tagid": "openx_35"}, {"dur": 25,"tagid": "openx_25"}, {"dur": 20,"tagid": "openx_20"}]}}}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"prebid": {"bidder": {"openx_vast_bidder": {"tags": [{"dur": 35,"tagid": "openx_35"}, {"dur": 25,"tagid": "openx_25"}, {"dur": 20,"tagid": "openx_20"}]}}}}`)}, + }, + }, + impData: []*types.ImpData{ + {ImpID: "imp1"}, + }, + }, + expectedOutput: output{ + reqs: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{"prebid": {"bidder": {}}}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"prebid": {"bidder": {"openx_vast_bidder":{"tags":[{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"prebid": {"bidder": {"openx_vast_bidder":{"tags":[{"dur":25,"tagid":"openx_25"}]}}}}`)}, + }, + }, + blockedTags: []map[string][]string{ + {"openx_vast_bidder": []string{"openx_35"}}, + }, + }, + }, + { + testName: "test_single_impression_multiple_vast_partners_no_exclusions", + input: inputParams{ + request: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1", Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":15,"tagid":"spotx_15"},{"dur":25,"tagid":"spotx_25"},{"dur":30,"tagid":"spotx_30"}]},"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}}}`)}, + }, + }, + generatedRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":15,"tagid":"spotx_15"},{"dur":25,"tagid":"spotx_25"},{"dur":30,"tagid":"spotx_30"}]},"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":15,"tagid":"spotx_15"},{"dur":25,"tagid":"spotx_25"},{"dur":30,"tagid":"spotx_30"}]},"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":15,"tagid":"spotx_15"},{"dur":25,"tagid":"spotx_25"},{"dur":30,"tagid":"spotx_30"}]},"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}}}`)}, + }, + }, + impData: []*types.ImpData{}, + }, + expectedOutput: output{ + reqs: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{"prebid":{"bidder":{}}}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":20,"tagid":"openx_20"}]},"spotx_vast_bidder":{"tags":[{"dur":15,"tagid":"spotx_15"}]}}}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":25,"tagid":"openx_25"}]},"spotx_vast_bidder":{"tags":[{"dur":25,"tagid":"spotx_25"},{"dur":30,"tagid":"spotx_30"}]}}}}`)}, + }, + }, + blockedTags: []map[string][]string{}, + }, + }, + { + testName: "test_single_impression_multiple_vast_partners_with_exclusions", + input: inputParams{ + request: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1", Ext: []byte(`{"prebid": { "bidder": { "spotx_vast_bidder":{"tags":[{"dur":15,"tagid":"spotx_15"},{"dur":25,"tagid":"spotx_25"},{"dur":35,"tagid":"spotx_35"}]},"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":40,"tagid":"openx_40"}]}}}}`)}, + }, + }, + generatedRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":15,"tagid":"spotx_15"},{"dur":25,"tagid":"spotx_25"},{"dur":35,"tagid":"spotx_35"}]},"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":40,"tagid":"openx_40"}]}}}}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":15,"tagid":"spotx_15"},{"dur":25,"tagid":"spotx_25"},{"dur":35,"tagid":"spotx_35"}]},"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":40,"tagid":"openx_40"}]}}}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":15,"tagid":"spotx_15"},{"dur":25,"tagid":"spotx_25"},{"dur":35,"tagid":"spotx_35"}]},"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":40,"tagid":"openx_40"}]}}}}`)}, + }, + }, + impData: []*types.ImpData{ + {ImpID: "imp1"}, + }, + }, + expectedOutput: output{ + reqs: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{"prebid":{"bidder":{}}}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":15,"tagid":"spotx_15"}]}}}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":25,"tagid":"openx_25"}]},"spotx_vast_bidder":{"tags":[{"dur":25,"tagid":"spotx_25"}]}}}}`)}, + }, + }, + blockedTags: []map[string][]string{ + {"openx_vast_bidder": []string{"openx_35", "openx_40"}, "spotx_vast_bidder": []string{"spotx_35"}}, + }, + }, + }, + { + testName: "test_multi_impression_multi_partner_no_exclusions", + input: inputParams{ + request: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1", Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp2", Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":30,"tagid":"spotx_30"},{"dur":40,"tagid":"spotx_40"}]}}}}`)}, + }, + }, + generatedRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp2_1", Video: &openrtb2.Video{MinDuration: 5, MaxDuration: 30}, Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":30,"tagid":"spotx_30"},{"dur":40,"tagid":"spotx_40"}]}}}}`)}, + }, + }, + impData: nil, + }, + expectedOutput: output{ + reqs: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{"prebid":{"bidder":{}}}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":25,"tagid":"openx_25"}]}}}}`)}, + {ID: "imp2_1", Video: &openrtb2.Video{MinDuration: 5, MaxDuration: 30}, Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":30,"tagid":"spotx_30"}]}}}}`)}, + }, + }, + blockedTags: nil, + }, + }, + { + testName: "test_multi_impression_multi_partner_with_exclusions", + input: inputParams{ + request: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1", Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp2", Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":30,"tagid":"spotx_30"},{"dur":40,"tagid":"spotx_40"}]}}}}`)}, + }, + }, + generatedRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp2_1", Video: &openrtb2.Video{MinDuration: 5, MaxDuration: 30}, Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":30,"tagid":"spotx_30"},{"dur":40,"tagid":"spotx_40"}]}}}}`)}, + }, + }, + impData: []*types.ImpData{ + {ImpID: "imp1"}, + {ImpID: "imp2"}, + }, + }, + expectedOutput: output{ + reqs: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{"prebid":{"bidder":{}}}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":20,"tagid":"openx_20"}]}}}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"prebid":{"bidder":{"openx_vast_bidder":{"tags":[{"dur":25,"tagid":"openx_25"}]}}}}`)}, + {ID: "imp2_1", Video: &openrtb2.Video{MinDuration: 5, MaxDuration: 30}, Ext: []byte(`{"prebid":{"bidder":{"spotx_vast_bidder":{"tags":[{"dur":30,"tagid":"spotx_30"}]}}}}`)}, + }, + }, + blockedTags: []map[string][]string{ + {"openx_vast_bidder": []string{"openx_35"}}, + {"spotx_vast_bidder": []string{"spotx_40"}}, + }, + }, + }, + } + + for _, tc := range tt { + tc := tc + t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + + deps := ctvEndpointDeps{request: tc.input.request, impData: tc.input.impData} + deps.readImpExtensionsAndTags() + + outputBids := tc.input.generatedRequest + deps.filterImpsVastTagsByDuration(outputBids) + + assert.Equal(t, tc.expectedOutput.reqs, *outputBids, "Expected length of impressions array was %d but actual was %d", tc.expectedOutput.reqs, outputBids) + + for i, datum := range deps.impData { + assert.Equal(t, tc.expectedOutput.blockedTags[i], datum.BlockedVASTTags, "Expected and actual impData was different") + } + }) + } +} + +func TestGetBidDuration(t *testing.T) { + type args struct { + bid *openrtb2.Bid + reqExt *openrtb_ext.ExtRequestAdPod + config []*types.ImpAdPodConfig + defaultDuration int64 + } + type want struct { + duration int64 + status constant.BidStatus + } + var tests = []struct { + name string + args args + want want + expect int + }{ + { + name: "nil_bid_ext", + args: args{ + bid: &openrtb2.Bid{}, + reqExt: nil, + config: nil, + defaultDuration: 100, + }, + want: want{ + duration: 100, + status: constant.StatusOK, + }, + }, + { + name: "use_default_duration", + args: args{ + bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"tmp":123}`), + }, + reqExt: nil, + config: nil, + defaultDuration: 100, + }, + want: want{ + duration: 100, + status: constant.StatusOK, + }, + }, + { + name: "invalid_duration_in_bid_ext", + args: args{ + bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"prebid":{"video":{"duration":"invalid"}}}`), + }, + reqExt: nil, + config: nil, + defaultDuration: 100, + }, + want: want{ + duration: 100, + status: constant.StatusOK, + }, + }, + { + name: "0sec_duration_in_bid_ext", + args: args{ + bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"prebid":{"video":{"duration":0}}}`), + }, + reqExt: nil, + config: nil, + defaultDuration: 100, + }, + want: want{ + duration: 100, + status: constant.StatusOK, + }, + }, + { + name: "negative_duration_in_bid_ext", + args: args{ + bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"prebid":{"video":{"duration":-30}}}`), + }, + reqExt: nil, + config: nil, + defaultDuration: 100, + }, + want: want{ + duration: 100, + status: constant.StatusOK, + }, + }, + { + name: "30sec_duration_in_bid_ext", + args: args{ + bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"prebid":{"video":{"duration":30}}}`), + }, + reqExt: nil, + config: nil, + defaultDuration: 100, + }, + want: want{ + duration: 30, + status: constant.StatusOK, + }, + }, + { + name: "duration_matching_empty", + args: args{ + bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"prebid":{"video":{"duration":30}}}`), + }, + reqExt: &openrtb_ext.ExtRequestAdPod{ + VideoAdDurationMatching: "", + }, + config: nil, + defaultDuration: 100, + }, + want: want{ + duration: 30, + status: constant.StatusOK, + }, + }, + { + name: "duration_matching_exact", + args: args{ + bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"prebid":{"video":{"duration":30}}}`), + }, + reqExt: &openrtb_ext.ExtRequestAdPod{ + VideoAdDurationMatching: openrtb_ext.OWExactVideoAdDurationMatching, + }, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 10}, + {MaxDuration: 20}, + {MaxDuration: 30}, + {MaxDuration: 40}, + }, + defaultDuration: 100, + }, + want: want{ + duration: 30, + status: constant.StatusOK, + }, + }, + { + name: "duration_matching_exact_not_present", + args: args{ + bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"prebid":{"video":{"duration":35}}}`), + }, + reqExt: &openrtb_ext.ExtRequestAdPod{ + VideoAdDurationMatching: openrtb_ext.OWExactVideoAdDurationMatching, + }, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 10}, + {MaxDuration: 20}, + {MaxDuration: 30}, + {MaxDuration: 40}, + }, + defaultDuration: 100, + }, + want: want{ + duration: 35, + status: constant.StatusDurationMismatch, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + duration, status := getBidDuration(tt.args.bid, tt.args.reqExt, tt.args.config, tt.args.defaultDuration) + assert.Equal(t, tt.want.duration, duration) + assert.Equal(t, tt.want.status, status) + }) + } +} + +func Test_getDurationBasedOnDurationMatchingPolicy(t *testing.T) { + type args struct { + duration int64 + policy openrtb_ext.OWVideoAdDurationMatchingPolicy + config []*types.ImpAdPodConfig + } + type want struct { + duration int64 + status constant.BidStatus + } + tests := []struct { + name string + args args + want want + }{ + { + name: "empty_duration_policy", + args: args{ + duration: 10, + policy: "", + config: []*types.ImpAdPodConfig{ + {MaxDuration: 10}, + {MaxDuration: 20}, + {MaxDuration: 30}, + {MaxDuration: 40}, + }, + }, + want: want{ + duration: 10, + status: constant.StatusOK, + }, + }, + { + name: "policy_exact", + args: args{ + duration: 10, + policy: openrtb_ext.OWExactVideoAdDurationMatching, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 10}, + {MaxDuration: 20}, + {MaxDuration: 30}, + {MaxDuration: 40}, + }, + }, + want: want{ + duration: 10, + status: constant.StatusOK, + }, + }, + { + name: "policy_exact_didnot_match", + args: args{ + duration: 15, + policy: openrtb_ext.OWExactVideoAdDurationMatching, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 10}, + {MaxDuration: 20}, + {MaxDuration: 30}, + {MaxDuration: 40}, + }, + }, + want: want{ + duration: 15, + status: constant.StatusDurationMismatch, + }, + }, + { + name: "policy_roundup_exact", + args: args{ + duration: 20, + policy: openrtb_ext.OWRoundupVideoAdDurationMatching, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 10}, + {MaxDuration: 20}, + {MaxDuration: 30}, + {MaxDuration: 40}, + }, + }, + want: want{ + duration: 20, + status: constant.StatusOK, + }, + }, + { + name: "policy_roundup", + args: args{ + duration: 25, + policy: openrtb_ext.OWRoundupVideoAdDurationMatching, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 10}, + {MaxDuration: 20}, + {MaxDuration: 30}, + {MaxDuration: 40}, + }, + }, + want: want{ + duration: 30, + status: constant.StatusOK, + }, + }, + { + name: "policy_roundup_didnot_match", + args: args{ + duration: 45, + policy: openrtb_ext.OWRoundupVideoAdDurationMatching, + config: []*types.ImpAdPodConfig{ + {MaxDuration: 10}, + {MaxDuration: 20}, + {MaxDuration: 30}, + {MaxDuration: 40}, + }, + }, + want: want{ + duration: 45, + status: constant.StatusDurationMismatch, + }, + }, + + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + duration, status := getDurationBasedOnDurationMatchingPolicy(tt.args.duration, tt.args.policy, tt.args.config) + assert.Equal(t, tt.want.duration, duration) + assert.Equal(t, tt.want.status, status) + }) + } +} + +func TestCreateAdPodBidResponse(t *testing.T) { + type args struct { + resp *openrtb2.BidResponse + } + type want struct { + resp *openrtb2.BidResponse + } + tests := []struct { + name string + args args + want want + }{ + { + name: "sample bidresponse", + args: args{ + resp: &openrtb2.BidResponse{ + ID: "id1", + Cur: "USD", + CustomData: "custom", + }, + }, + want: want{ + resp: &openrtb2.BidResponse{ + ID: "id1", + Cur: "USD", + CustomData: "custom", + SeatBid: make([]openrtb2.SeatBid, 0), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + deps := ctvEndpointDeps{ + request: &openrtb2.BidRequest{ + ID: "1", + }, + } + actual := deps.createAdPodBidResponse(tt.args.resp, nil) + assert.Equal(t, tt.want.resp, actual) + }) + + } +} + +func TestSetBidExtParams(t *testing.T) { + type args struct { + impData []*types.ImpData + } + type want struct { + impData []*types.ImpData + } + tests := []struct { + name string + args args + want want + }{ + { + name: "sample", + args: args{ + impData: []*types.ImpData{ + { + Bid: &types.AdPodBid{ + Bids: []*types.Bid{ + { + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"prebid": {"video": {} },"adpod": {}}`), + }, + Duration: 10, + Status: 1, + }, + }, + }, + }, + }, + }, + want: want{ + impData: []*types.ImpData{ + { + Bid: &types.AdPodBid{ + Bids: []*types.Bid{ + { + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"prebid": {"video": {"duration":10} },"adpod": {"aprc":1}}`), + }, + Duration: 10, + Status: 1, + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + deps := ctvEndpointDeps{ + impData: tt.args.impData, + } + deps.setBidExtParams() + assert.Equal(t, tt.want.impData[0].Bid.Bids[0].Ext, deps.impData[0].Bid.Bids[0].Ext) + }) + } +} + +func TestGetAdPodExt(t *testing.T) { + type args struct { + resp *openrtb2.BidResponse + } + type want struct { + data json.RawMessage + } + tests := []struct { + name string + args args + want want + }{ + { + name: "nil-ext", + args: args{ + resp: &openrtb2.BidResponse{ + ID: "resp1", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "b1", + }, + { + ID: "b2", + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + want: want{ + data: json.RawMessage(`{"adpod":{"bidresponse":{"id":"resp1","seatbid":[{"bid":[{"id":"b1","impid":"","price":0},{"id":"b2","impid":"","price":0}],"seat":"pubmatic"}]},"config":{"imp1":{"vidext":{"adpod":{}}}}}}`), + }, + }, + { + name: "non-nil-ext", + args: args{ + resp: &openrtb2.BidResponse{ + ID: "resp1", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "b1", + }, + { + ID: "b2", + }, + }, + Seat: "pubmatic", + }, + }, + Ext: json.RawMessage(`{"xyz":10}`), + }, + }, + want: want{ + data: json.RawMessage(`{"xyz":10,"adpod":{"bidresponse":{"id":"resp1","seatbid":[{"bid":[{"id":"b1","impid":"","price":0},{"id":"b2","impid":"","price":0}],"seat":"pubmatic"}]},"config":{"imp1":{"vidext":{"adpod":{}}}}}}`), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + deps := ctvEndpointDeps{ + impData: []*types.ImpData{ + { + ImpID: "imp1", + VideoExt: &openrtb_ext.ExtVideoAdPod{ + AdPod: &openrtb_ext.VideoAdPod{}, + }, + Bid: &types.AdPodBid{ + Bids: []*types.Bid{}, + }, + }, + }, + request: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1"}, + }, + }, + } + actual := deps.getBidResponseExt(tt.args.resp) + assert.Equal(t, string(tt.want.data), string(actual)) + }) + } +} + +func TestRecordAdPodRejectedBids(t *testing.T) { + + type args struct { + bids types.AdPodBid + } + + type want struct { + expectedCalls int + } + + tests := []struct { + description string + args args + want want + }{ + { + description: "multiple rejected bids", + args: args{ + bids: types.AdPodBid{ + Bids: []*types.Bid{ + { + Bid: &openrtb2.Bid{}, + Status: constant.StatusCategoryExclusion, + Seat: "pubmatic", + }, + { + Bid: &openrtb2.Bid{}, + Status: constant.StatusWinningBid, + Seat: "pubmatic", + }, + { + Bid: &openrtb2.Bid{}, + Status: constant.StatusOK, + Seat: "pubmatic", + }, + { + Bid: &openrtb2.Bid{}, + Status: 100, + Seat: "pubmatic", + }, + }, + }, + }, + want: want{ + expectedCalls: 2, + }, + }, + } + + for _, test := range tests { + me := &metrics.MetricsEngineMock{} + me.On("RecordRejectedBids", mock.Anything, mock.Anything, mock.Anything).Return() + + deps := ctvEndpointDeps{ + endpointDeps: endpointDeps{ + metricsEngine: me, + }, + impData: []*types.ImpData{ + { + Bid: &test.args.bids, + }, + }, + } + + deps.recordRejectedAdPodBids("pub_001") + me.AssertNumberOfCalls(t, "RecordRejectedBids", test.want.expectedCalls) + } +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/origbidcpmusd.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/origbidcpmusd.json new file mode 100644 index 00000000000..411d6db3120 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/origbidcpmusd.json @@ -0,0 +1,76 @@ +{ + "description": "bid.ext.origbidcpmusd with bid.ext.origbidcpm in USD for wrapper logger and wrapper tracker", + "config": { + "assertBidExt": true, + "currencyRates":{ + "USD": { + "MXN": 20.07 + }, + "INR": { + "MXN": 0.25 + } + }, + "mockBidders": [ + {"bidderName": "pubmatic", "currency": "MXN", "price": 5.00} + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "pubmatic": { + "placementId": 12883451 + } + } + } + ], + "cur": ["INR"], + "ext": { + "prebid": { + "aliases": { + "unknown": "pubmatic" + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "INR", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "pubmatic-bid", + "impid": "my-imp-id", + "price": 20, + "ext": { + "origbidcpm": 5, + "origbidcur": "MXN", + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "banner" + }, + "origbidcpmusd": 0.2491280518186348 + } + } + ], + "seat": "pubmatic" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index e3e7803e551..ed79bd5f9bc 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -90,6 +90,7 @@ type testConfigValues struct { CurrencyRates map[string]map[string]float64 `json:"currencyRates"` MockBidders []mockBidderHandler `json:"mockBidders"` RealParamsValidator bool `json:"realParamsValidator"` + AssertBidExt bool `json:"assertbidext"` } type brokenExchange struct{} @@ -1409,6 +1410,7 @@ func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCo type mockPlanBuilder struct { entrypointPlan hooks.Plan[hookstage.Entrypoint] rawAuctionPlan hooks.Plan[hookstage.RawAuctionRequest] + beforeRequestValidation hooks.Plan[hookstage.BeforeValidationRequest] processedAuctionPlan hooks.Plan[hookstage.ProcessedAuctionRequest] bidderRequestPlan hooks.Plan[hookstage.BidderRequest] rawBidderResponsePlan hooks.Plan[hookstage.RawBidderResponse] @@ -1424,6 +1426,10 @@ func (m mockPlanBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hoo return m.rawAuctionPlan } +func (m mockPlanBuilder) PlanForValidationStage(_ string, _ *config.Account) hooks.Plan[hookstage.BeforeValidationRequest] { + return m.beforeRequestValidation +} + func (m mockPlanBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { return m.processedAuctionPlan } @@ -1480,6 +1486,14 @@ func (m mockRejectionHook) HandleRawAuctionHook( return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, m.err } +func (m mockRejectionHook) HandleBeforeValidationHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.BeforeValidationRequestPayload, +) (hookstage.HookResult[hookstage.BeforeValidationRequestPayload], error) { + return hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{Reject: true, NbrCode: m.nbr}, m.err +} + func (m mockRejectionHook) HandleProcessedAuctionHook( _ context.Context, _ hookstage.ModuleInvocationContext, diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 05802bbd506..e6e1e3e739d 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -318,7 +318,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re LegacyLabels: labels, GlobalPrivacyControlHeader: secGPC, PubID: labels.PubID, - HookExecutor: hookexecution.EmptyHookExecutor{}, + HookExecutor: &hookexecution.EmptyHookExecutor{}, TmaxAdjustments: deps.tmaxAdjustments, Activities: activityControl, } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 70a37aab5df..849a8307d33 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1171,7 +1171,7 @@ func TestVideoAuctionResponseHeaders(t *testing.T) { givenTestFile: "sample-requests/video/video_valid_sample.json", expectedStatus: 200, expectedHeaders: func(h http.Header) { - h.Set("X-Prebid", "pbs-go/unknown") + h.Set("X-Prebid", "owpbs-go/unknown") h.Set("Content-Type", "application/json") }, }, { @@ -1179,7 +1179,7 @@ func TestVideoAuctionResponseHeaders(t *testing.T) { givenTestFile: "sample-requests/video/video_invalid_sample.json", expectedStatus: 500, expectedHeaders: func(h http.Header) { - h.Set("X-Prebid", "pbs-go/unknown") + h.Set("X-Prebid", "owpbs-go/unknown") }, }, } diff --git a/errortypes/code.go b/errortypes/code.go index 1de5e648cef..d72dfe1a825 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -17,6 +17,12 @@ const ( TmaxTimeoutErrorCode FailedToMarshalErrorCode FailedToUnmarshalErrorCode + + // NYC: shall we have different range for OW error codes to avoid change in codes with introduction of new PBS error codes. + NoBidPriceErrorCode + BidderFailedSchemaValidationErrorCode + AdpodPrefilteringErrorCode + BidRejectionFloorsErrorCode ) // Defines numeric codes for well-known warnings. @@ -31,6 +37,7 @@ const ( AdServerTargetingWarningCode BidAdjustmentWarningCode FloorBidRejectionWarningCode + AdpodPostFilteringWarningCode ) // Coder provides an error or warning code with severity. diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index d31c4166b06..7019e9d04e2 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -251,3 +251,73 @@ func (err *FailedToMarshal) Code() int { func (err *FailedToMarshal) Severity() Severity { return SeverityFatal } + +// BidderFailedSchemaValidation is used at the request validation step, +// when the bidder parameters fail the schema validation, we want to +// continue processing the request and still return an error message. +type BidderFailedSchemaValidation struct { + Message string +} + +func (err *BidderFailedSchemaValidation) Error() string { + return err.Message +} + +func (err *BidderFailedSchemaValidation) Code() int { + return BidderFailedSchemaValidationErrorCode +} + +func (err *BidderFailedSchemaValidation) Severity() Severity { + return SeverityWarning +} + +// NoBidPrice should be used when vast response doesn't contain any price value +type NoBidPrice struct { + Message string +} + +func (err *NoBidPrice) Error() string { + return err.Message +} + +func (err *NoBidPrice) Code() int { + return NoBidPriceErrorCode +} + +func (err *NoBidPrice) Severity() Severity { + return SeverityWarning +} + +// AdpodPrefiltering should be used when ctv impression algorithm not able to generate impressions +type AdpodPrefiltering struct { + Message string +} + +func (err *AdpodPrefiltering) Error() string { + return err.Message +} + +func (err *AdpodPrefiltering) Code() int { + return AdpodPrefilteringErrorCode +} + +func (err *AdpodPrefiltering) Severity() Severity { + return SeverityFatal +} + +// AdpodPostFiltering should be used when vast response doesn't contain any price value +type AdpodPostFiltering struct { + Message string +} + +func (err *AdpodPostFiltering) Error() string { + return err.Message +} + +func (err *AdpodPostFiltering) Code() int { + return AdpodPostFilteringWarningCode +} + +func (err *AdpodPostFiltering) Severity() Severity { + return SeverityWarning +} diff --git a/errortypes/errortypes_test.go b/errortypes/errortypes_test.go new file mode 100644 index 00000000000..cbe35ed3c6c --- /dev/null +++ b/errortypes/errortypes_test.go @@ -0,0 +1,177 @@ +package errortypes + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestErrors(t *testing.T) { + type args struct { + err error + } + type want struct { + errorMessage string + code int + severity Severity + } + tests := []struct { + name string + args args + want want + }{ + { + name: `normal_error`, + args: args{ + err: fmt.Errorf(`normal_error`), + }, + want: want{ + errorMessage: `normal_error`, + code: UnknownErrorCode, + severity: SeverityUnknown, + }, + }, + { + name: `Timeout`, + args: args{ + err: &Timeout{Message: `Timeout_ErrorMessage`}, + }, + want: want{ + errorMessage: `Timeout_ErrorMessage`, + code: TimeoutErrorCode, + severity: SeverityFatal, + }, + }, + { + name: `BadInput`, + args: args{ + err: &BadInput{Message: `BadInput_ErrorMessage`}, + }, + want: want{ + errorMessage: `BadInput_ErrorMessage`, + code: BadInputErrorCode, + severity: SeverityFatal, + }, + }, + { + name: `BlacklistedApp`, + args: args{ + err: &BlacklistedApp{Message: `BlacklistedApp_ErrorMessage`}, + }, + want: want{ + errorMessage: `BlacklistedApp_ErrorMessage`, + code: BlacklistedAppErrorCode, + severity: SeverityFatal, + }, + }, + { + name: `AcctRequired`, + args: args{ + err: &AcctRequired{Message: `AcctRequired_ErrorMessage`}, + }, + want: want{ + errorMessage: `AcctRequired_ErrorMessage`, + code: AcctRequiredErrorCode, + severity: SeverityFatal, + }, + }, + { + name: `BadServerResponse`, + args: args{ + err: &BadServerResponse{Message: `BadServerResponse_ErrorMessage`}, + }, + want: want{ + errorMessage: `BadServerResponse_ErrorMessage`, + code: BadServerResponseErrorCode, + severity: SeverityFatal, + }, + }, + { + name: `FailedToRequestBids`, + args: args{ + err: &FailedToRequestBids{Message: `FailedToRequestBids_ErrorMessage`}, + }, + want: want{ + errorMessage: `FailedToRequestBids_ErrorMessage`, + code: FailedToRequestBidsErrorCode, + severity: SeverityFatal, + }, + }, + { + name: `BidderTemporarilyDisabled`, + args: args{ + err: &BidderTemporarilyDisabled{Message: `BidderTemporarilyDisabled_ErrorMessage`}, + }, + want: want{ + errorMessage: `BidderTemporarilyDisabled_ErrorMessage`, + code: BidderTemporarilyDisabledErrorCode, + severity: SeverityWarning, + }, + }, + { + name: `Warning`, + args: args{ + err: &Warning{Message: `Warning_ErrorMessage`, WarningCode: UnknownWarningCode}, + }, + want: want{ + errorMessage: `Warning_ErrorMessage`, + code: UnknownWarningCode, + severity: SeverityWarning, + }, + }, + { + name: `BidderFailedSchemaValidation`, + args: args{ + err: &BidderFailedSchemaValidation{Message: `BidderFailedSchemaValidation_ErrorMessage`}, + }, + want: want{ + errorMessage: `BidderFailedSchemaValidation_ErrorMessage`, + code: BidderFailedSchemaValidationErrorCode, + severity: SeverityWarning, + }, + }, + { + name: `NoBidPrice`, + args: args{ + err: &NoBidPrice{Message: `NoBidPrice_ErrorMessage`}, + }, + want: want{ + errorMessage: `NoBidPrice_ErrorMessage`, + code: NoBidPriceErrorCode, + severity: SeverityWarning, + }, + }, + { + name: `AdpodPrefiltering`, + args: args{ + err: &AdpodPrefiltering{Message: `AdpodPrefiltering_ErrorMessage`}, + }, + want: want{ + errorMessage: `AdpodPrefiltering_ErrorMessage`, + code: AdpodPrefilteringErrorCode, + severity: SeverityFatal, + }, + }, + { + name: `AdpodPostFiltering`, + args: args{ + err: &AdpodPostFiltering{Message: `AdpodPostFiltering_ErrorMessage`}, + }, + want: want{ + errorMessage: `AdpodPostFiltering_ErrorMessage`, + code: AdpodPostFilteringWarningCode, + severity: SeverityWarning, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want.errorMessage, tt.args.err.Error()) + if code, ok := tt.args.err.(Coder); ok { + assert.Equal(t, tt.want.code, code.Code()) + assert.Equal(t, tt.want.severity, code.Severity()) + } + }) + } +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 825dd411a96..0e86a73d257 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -160,6 +160,7 @@ import ( "github.com/prebid/prebid-server/v2/adapters/smilewanted" "github.com/prebid/prebid-server/v2/adapters/sonobi" "github.com/prebid/prebid-server/v2/adapters/sovrn" + "github.com/prebid/prebid-server/v2/adapters/spotx" "github.com/prebid/prebid-server/v2/adapters/sspBC" "github.com/prebid/prebid-server/v2/adapters/stroeerCore" "github.com/prebid/prebid-server/v2/adapters/suntContent" @@ -175,6 +176,7 @@ import ( "github.com/prebid/prebid-server/v2/adapters/undertone" "github.com/prebid/prebid-server/v2/adapters/unicorn" "github.com/prebid/prebid-server/v2/adapters/unruly" + "github.com/prebid/prebid-server/v2/adapters/vastbidder" "github.com/prebid/prebid-server/v2/adapters/videobyte" "github.com/prebid/prebid-server/v2/adapters/videoheroes" "github.com/prebid/prebid-server/v2/adapters/vidoomy" @@ -359,6 +361,8 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderSonobi: sonobi.Builder, openrtb_ext.BidderSovrn: sovrn.Builder, openrtb_ext.BidderSspBC: sspBC.Builder, + openrtb_ext.BidderSpotX: spotx.Builder, + openrtb_ext.BidderStreamkey: adtelligent.Builder, openrtb_ext.BidderSuntContent: suntContent.Builder, openrtb_ext.BidderStroeerCore: stroeerCore.Builder, openrtb_ext.BidderTaboola: taboola.Builder, @@ -373,6 +377,8 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderUndertone: undertone.Builder, openrtb_ext.BidderUnicorn: unicorn.Builder, openrtb_ext.BidderUnruly: unruly.Builder, + openrtb_ext.BidderVASTBidder: vastbidder.Builder, + openrtb_ext.BidderValueImpression: apacdex.Builder, openrtb_ext.BidderVideoByte: videobyte.Builder, openrtb_ext.BidderVideoHeroes: videoheroes.Builder, openrtb_ext.BidderVidoomy: vidoomy.Builder, diff --git a/exchange/auction.go b/exchange/auction.go index 17a843fb5ed..72c1ff80f9d 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -334,12 +334,18 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, // makeVAST returns some VAST XML for the given bid. If AdM is defined, // it takes precedence. Otherwise the Nurl will be wrapped in a redirect tag. func makeVAST(bid *openrtb2.Bid) string { + wrapperVASTTemplate := `` + + `prebid.org wrapper` + + `` + + `` + + `` + if bid.AdM == "" { - return `` + - `prebid.org wrapper` + - `` + - `` + - `` + return fmt.Sprintf(wrapperVASTTemplate, bid.NURL) // set nurl as VASTAdTagURI + } + + if strings.HasPrefix(bid.AdM, "http") { // check if it contains URL + return fmt.Sprintf(wrapperVASTTemplate, bid.AdM) // set adm as VASTAdTagURI } return bid.AdM } diff --git a/exchange/auction_test.go b/exchange/auction_test.go index 10c4b9a5e67..2339453d427 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -45,6 +45,20 @@ func TestMakeVASTNurl(t *testing.T) { assert.Equal(t, expect, vast) } +func TestMakeVASTAdmContainsURI(t *testing.T) { + const url = "http://myvast.com/1.xml" + const expect = `` + + `prebid.org wrapper` + + `` + + `` + + `` + bid := &openrtb2.Bid{ + AdM: url, + } + vast := makeVAST(bid) + assert.Equal(t, expect, vast) +} + func TestBuildCacheString(t *testing.T) { testCases := []struct { description string diff --git a/exchange/bidder.go b/exchange/bidder.go index ce502e53d84..de2ce294790 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -82,6 +82,10 @@ type extraAuctionResponseInfo struct { bidderResponseStartTime time.Time } +const ( + bidderGroupM = "groupm" +) + const ImpIdReqBody = "Stored bid response for impression id: " // Possible values of compression types Prebid Server can support for bidder compression @@ -256,10 +260,12 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if httpInfo.err == nil { extraRespInfo.respProcessingStartTime = time.Now() + httpInfo.request.BidderName = bidderRequest.BidderName bidResponse, moreErrs := bidder.Bidder.MakeBids(bidderRequest.BidRequest, httpInfo.request, httpInfo.response) errs = append(errs, moreErrs...) if bidResponse != nil { + recordVASTTagType(bidder.me, bidResponse, bidder.BidderName) reject := hookExecutor.ExecuteRawBidderResponseStage(bidResponse, string(bidder.BidderName)) if reject != nil { errs = append(errs, reject) @@ -273,6 +279,13 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde bidderRequest.BidRequest.Cur = []string{defaultCurrency} } + // WL and WTK only accepts USD so we would need to convert prices to USD before sending data to them. But, + // PBS-Core's getAuctionCurrencyRates() is not exposed and would be too much work to do so. Also, would be a repeated work for SSHB to convert each bid's price + // Hence, we would send a USD conversion rate to SSHB for each bid beside prebid's origbidcpm and origbidcur + // Ex. req.cur=INR and resp.cur=JYP. Hence, we cannot use origbidcpm and origbidcur and would need a dedicated field for USD conversion rates + var conversionRateUSD float64 + selectedCur := "USD" + // Try to get a conversion rate // Try to get the first currency from request.cur having a match in the rate converter, // and use it as currency @@ -281,10 +294,21 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde for _, bidReqCur := range bidderRequest.BidRequest.Cur { if conversionRate, err = conversions.GetRate(bidResponse.Currency, bidReqCur); err == nil { seatBidMap[bidderRequest.BidderName].Currency = bidReqCur + selectedCur = bidReqCur break } } + // no need of conversionRateUSD if + // - bids with conversionRate = 0 would be a dropped + // - response would be in USD + if conversionRate != float64(0) && selectedCur != "USD" { + conversionRateUSD, err = conversions.GetRate(bidResponse.Currency, "USD") + if err != nil { + errs = append(errs, fmt.Errorf("failed to get USD conversion rate for WL and WTK %v", err)) + } + } + // Only do this for request from mobile app if bidderRequest.BidRequest.App != nil { for i := 0; i < len(bidResponse.Bids); i++ { @@ -350,18 +374,22 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde } adjustmentFactor := 1.0 - if givenAdjustment, ok := bidRequestOptions.bidAdjustments[(strings.ToLower(bidderName.String()))]; ok { - adjustmentFactor = givenAdjustment - } else if givenAdjustment, ok := bidRequestOptions.bidAdjustments[(strings.ToLower(bidderRequest.BidderName.String()))]; ok { - adjustmentFactor = givenAdjustment - } + if bidderName != bidderGroupM { + if givenAdjustment, ok := bidRequestOptions.bidAdjustments[(strings.ToLower(bidderName.String()))]; ok { + adjustmentFactor = givenAdjustment + } else if givenAdjustment, ok := bidRequestOptions.bidAdjustments[(strings.ToLower(bidderRequest.BidderName.String()))]; ok { + adjustmentFactor = givenAdjustment + } + } originalBidCpm := 0.0 currencyAfterAdjustments := "" + originalBidCPMUSD := 0.0 if bidResponse.Bids[i].Bid != nil { originalBidCpm = bidResponse.Bids[i].Bid.Price bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate + originalBidCPMUSD = originalBidCpm * adjustmentFactor * conversionRateUSD bidType := getBidTypeForAdjustments(bidResponse.Bids[i].BidType, bidResponse.Bids[i].Bid.ImpID, bidderRequest.BidRequest.Imp) bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = bidadjustment.Apply(ruleToAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo, bidType) } @@ -378,13 +406,15 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde } seatBidMap[bidderName].Bids = append(seatBidMap[bidderName].Bids, &entities.PbsOrtbBid{ - Bid: bidResponse.Bids[i].Bid, - BidMeta: bidResponse.Bids[i].BidMeta, - BidType: bidResponse.Bids[i].BidType, - BidVideo: bidResponse.Bids[i].BidVideo, - DealPriority: bidResponse.Bids[i].DealPriority, - OriginalBidCPM: originalBidCpm, - OriginalBidCur: bidResponse.Currency, + Bid: bidResponse.Bids[i].Bid, + BidMeta: bidResponse.Bids[i].BidMeta, + BidType: bidResponse.Bids[i].BidType, + BidVideo: bidResponse.Bids[i].BidVideo, + DealPriority: bidResponse.Bids[i].DealPriority, + OriginalBidCPM: originalBidCpm, + OriginalBidCur: bidResponse.Currency, + BidTargets: bidResponse.Bids[i].BidTargets, + OriginalBidCPMUSD: originalBidCPMUSD, }) seatBidMap[bidderName].Currency = currencyAfterAdjustments } @@ -395,6 +425,9 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde } } else { errs = append(errs, httpInfo.err) + if errortypes.ReadCode(httpInfo.err) == errortypes.TimeoutErrorCode { + recordPartnerTimeout(ctx, bidderRequest.BidderLabels.PubID, bidder.BidderName.String()) + } } } seatBids := make([]*entities.PbsOrtbSeatBid, 0, len(seatBidMap)) @@ -509,6 +542,12 @@ func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall { ext.ResponseBody = string(httpInfo.response.Body) ext.Status = httpInfo.response.StatusCode } + + if nil != httpInfo.request.Params { + ext.Params = make(map[string]int) + ext.Params["ImpIndex"] = httpInfo.request.Params.ImpIndex + ext.Params["VASTTagIndex"] = httpInfo.request.Params.VASTTagIndex + } } return ext @@ -574,7 +613,6 @@ func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.Re // a loop of trying to report timeouts to the timeout notifications. go bidder.doTimeoutNotification(tb, req, logger) } - } return &httpCallInfo{ request: req, @@ -691,7 +729,7 @@ func (bidder *bidderAdapter) addClientTrace(ctx context.Context) context.Context TLSHandshakeDone: func(tls.ConnectionState, error) { tlsHandshakeTime := time.Now().Sub(tlsStart) - bidder.me.RecordTLSHandshakeTime(tlsHandshakeTime) + bidder.me.RecordTLSHandshakeTime(bidder.BidderName, tlsHandshakeTime) }, } return httptrace.WithClientTrace(ctx, trace) diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index b2314c8c428..fd868118191 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -343,7 +343,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { { Uri: server.URL, RequestBody: "requestJson", - RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "X-Prebid": {"pbs-go/test-version"}}, + RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "X-Prebid": {"owpbs-go/test-version"}}, ResponseBody: "responseJson", Status: 200, }, @@ -397,7 +397,7 @@ func TestSetGPCHeader(t *testing.T) { { Uri: server.URL, RequestBody: "requestJson", - RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "X-Prebid": {"pbs-go/unknown"}, "Sec-Gpc": {"1"}}, + RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "X-Prebid": {"owpbs-go/unknown"}, "Sec-Gpc": {"1"}}, ResponseBody: "responseJson", Status: 200, }, @@ -449,7 +449,7 @@ func TestSetGPCHeaderNil(t *testing.T) { { Uri: server.URL, RequestBody: "requestJson", - RequestHeaders: map[string][]string{"X-Prebid": {"pbs-go/unknown"}, "Sec-Gpc": {"1"}}, + RequestHeaders: map[string][]string{"X-Prebid": {"owpbs-go/unknown"}, "Sec-Gpc": {"1"}}, ResponseBody: "responseJson", Status: 200, }, @@ -1089,7 +1089,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { bidRequestCurrencies: []string{"EUR", "USD", "JPY"}, bidResponsesCurrency: "EUR", expectedPickedCurrency: "EUR", - expectedError: false, + expectedError: true, //conversionRateUSD fails as currency conversion in this test is default. rates: currency.Rates{ Conversions: map[string]map[string]float64{ "JPY": { @@ -1109,7 +1109,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { bidRequestCurrencies: []string{"JPY"}, bidResponsesCurrency: "JPY", expectedPickedCurrency: "JPY", - expectedError: false, + expectedError: true, //conversionRateUSD fails as currency conversion in this test is default. rates: currency.Rates{ Conversions: map[string]map[string]float64{ "JPY": { @@ -2160,7 +2160,7 @@ func TestCallRecordDNSTime(t *testing.T) { func TestCallRecordTLSHandshakeTime(t *testing.T) { // setup a mock metrics engine and its expectation metricsMock := &metrics.MetricsEngineMock{} - metricsMock.Mock.On("RecordTLSHandshakeTime", mock.Anything).Return() + metricsMock.Mock.On("RecordTLSHandshakeTime", mock.Anything, mock.Anything).Return() metricsMock.On("RecordOverheadTime", metrics.PreBidder, mock.Anything).Once() metricsMock.On("RecordBidderServerResponseTime", mock.Anything).Once() @@ -2730,7 +2730,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { Bids: []*entities.PbsOrtbBid{{ Bid: &openrtb2.Bid{ ID: "groupmImp1", - Price: 21, + Price: 7, }, DealPriority: 5, BidType: openrtb_ext.BidTypeVideo, @@ -2843,7 +2843,7 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { Bids: []*entities.PbsOrtbBid{{ Bid: &openrtb2.Bid{ ID: "groupmImp1", - Price: 14, + Price: 7, }, DealPriority: 5, BidType: openrtb_ext.BidTypeVideo, @@ -2957,10 +2957,11 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { ID: "groupmImp1", Price: 571.5994430039375, }, - DealPriority: 5, - BidType: openrtb_ext.BidTypeVideo, - OriginalBidCPM: 7, - OriginalBidCur: "USD", + DealPriority: 5, + BidType: openrtb_ext.BidTypeVideo, + OriginalBidCPM: 7, + OriginalBidCur: "USD", + OriginalBidCPMUSD: 7, }}, Seat: "groupm", Currency: "INR", @@ -2972,10 +2973,11 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { ID: "pubmaticImp1", Price: 244.97118985883034, }, - DealPriority: 4, - BidType: openrtb_ext.BidTypeBanner, - OriginalBidCPM: 3, - OriginalBidCur: "USD", + DealPriority: 4, + BidType: openrtb_ext.BidTypeBanner, + OriginalBidCPM: 3, + OriginalBidCur: "USD", + OriginalBidCPMUSD: 3, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "INR", diff --git a/exchange/entities/entities.go b/exchange/entities/entities.go index f106003d8ca..99f9c2f4849 100644 --- a/exchange/entities/entities.go +++ b/exchange/entities/entities.go @@ -22,6 +22,8 @@ type PbsOrtbSeatBid struct { HttpCalls []*openrtb_ext.ExtHttpCall // Seat defines whom these extra Bids belong to. Seat string + // bidderCoreName represents the core bidder id. + BidderCoreName openrtb_ext.BidderName } // PbsOrtbBid is a Bid returned by an AdaptedBidder. @@ -36,6 +38,7 @@ type PbsOrtbSeatBid struct { // PbsOrtbBid.DealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns. // PbsOrtbBid.DealTierSatisfied is set to true by exchange.updateHbPbCatDur if deal tier satisfied otherwise it will be set to false // PbsOrtbBid.GeneratedBidID is unique Bid id generated by prebid server if generate Bid id option is enabled in config +// pbsOrtbBid.originalBidCPMUSD is USD rate of the bid for WL and WTK as they only accepts USD type PbsOrtbBid struct { Bid *openrtb2.Bid BidMeta *openrtb_ext.ExtBidPrebidMeta @@ -50,4 +53,5 @@ type PbsOrtbBid struct { OriginalBidCPM float64 OriginalBidCur string TargetBidderCode string + OriginalBidCPMUSD float64 } diff --git a/exchange/events.go b/exchange/events.go index b20eea328f6..72dc935e277 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -3,14 +3,14 @@ package exchange import ( "time" - "github.com/prebid/prebid-server/v2/exchange/entities" - jsonpatch "gopkg.in/evanphx/json-patch.v4" - + "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/v2/analytics" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/endpoints/events" + "github.com/prebid/prebid-server/v2/exchange/entities" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/util/jsonutil" + jsonpatch "gopkg.in/evanphx/json-patch.v4" ) // eventTracking has configuration fields needed for adding event tracking to an auction response @@ -45,13 +45,10 @@ func getIntegrationType(requestExtPrebid *openrtb_ext.ExtRequestPrebid) string { } // modifyBidsForEvents adds bidEvents and modifies VAST AdM if necessary. -func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid { +func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, req *openrtb2.BidRequest, trackerURL string) map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid { for bidderName, seatBid := range seatBids { - modifyingVastXMLAllowed := ev.isModifyingVASTXMLAllowed(bidderName.String()) for _, pbsBid := range seatBid.Bids { - if modifyingVastXMLAllowed { - ev.modifyBidVAST(pbsBid, bidderName) - } + ev.modifyBidVAST(pbsBid, bidderName, seatBid.BidderCoreName, req, trackerURL) pbsBid.BidEvents = ev.makeBidExtEvents(pbsBid, bidderName) } } @@ -64,7 +61,7 @@ func (ev *eventTracking) isModifyingVASTXMLAllowed(bidderName string) bool { } // modifyBidVAST injects event Impression url if needed, otherwise returns original VAST string -func (ev *eventTracking) modifyBidVAST(pbsBid *entities.PbsOrtbBid, bidderName openrtb_ext.BidderName) { +func (ev *eventTracking) modifyBidVAST(pbsBid *entities.PbsOrtbBid, bidderName openrtb_ext.BidderName, bidderCoreName openrtb_ext.BidderName, req *openrtb2.BidRequest, trackerURL string) { bid := pbsBid.Bid if pbsBid.BidType != openrtb_ext.BidTypeVideo || len(bid.AdM) == 0 && len(bid.NURL) == 0 { return @@ -74,8 +71,16 @@ func (ev *eventTracking) modifyBidVAST(pbsBid *entities.PbsOrtbBid, bidderName o if len(pbsBid.GeneratedBidID) > 0 { bidID = pbsBid.GeneratedBidID } - if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bidID, bidderName.String(), ev.accountID, ev.auctionTimestampMs, ev.integrationType); ok { - bid.AdM = newVastXML + + if ev.isModifyingVASTXMLAllowed(bidderName.String()) { // condition added for ow fork + if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bidID, bidderName.String(), ev.accountID, ev.auctionTimestampMs, ev.integrationType); ok { + bid.AdM = newVastXML + } + } + + // always inject event trackers without checkign isModifyingVASTXMLAllowed + if newVastXML, injected, _ := events.InjectVideoEventTrackers(trackerURL, vastXML, bid, bidID, bidderName.String(), bidderCoreName.String(), ev.accountID, ev.auctionTimestampMs, req); injected { + bid.AdM = string(newVastXML) } } diff --git a/exchange/events_test.go b/exchange/events_test.go index 3d191f5f63c..2769984f4f5 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -1,9 +1,11 @@ package exchange import ( + "strings" "testing" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/exchange/entities" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" @@ -192,3 +194,108 @@ func Test_isEventAllowed(t *testing.T) { }) } } + +func TestModifyBidVAST(t *testing.T) { + type args struct { + bidReq *openrtb2.BidRequest + bid *openrtb2.Bid + } + type want struct { + tags []string + } + tests := []struct { + name string + args args + want want + }{ + { + name: "empty_adm", // expect adm contain vast tag with tracking events and VASTAdTagURI nurl contents + args: args{ + bidReq: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ID: "123", Video: &openrtb2.Video{}}}, + }, + bid: &openrtb2.Bid{ + AdM: "", + NURL: "nurl_contents", + ImpID: "123", + }, + }, + want: want{ + tags: []string{ + // ``, + // ``, + // ``, + // ``, + // "", + // "", + // "", + ``, + ``, + ``, + ``, + "", + "", + "", + }, + }, + }, + { + name: "adm_containing_url", // expect adm contain vast tag with tracking events and VASTAdTagURI adm url (previous value) contents + args: args{ + bidReq: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ID: "123", Video: &openrtb2.Video{}}}, + }, + bid: &openrtb2.Bid{ + AdM: "http://vast_tag_inline.xml", + NURL: "nurl_contents", + ImpID: "123", + }, + }, + want: want{ + tags: []string{ + // ``, + // ``, + // ``, + // ``, + // "", + // "", + // "", + ``, + ``, + ``, + ``, + "", + "", + "", + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ev := eventTracking{ + bidderInfos: config.BidderInfos{ + "somebidder": config.BidderInfo{ + ModifyingVastXmlAllowed: false, + }, + }, + } + ev.modifyBidVAST(&entities.PbsOrtbBid{ + Bid: tc.args.bid, + BidType: openrtb_ext.BidTypeVideo, + }, "somebidder", "coreBidder", tc.args.bidReq, "http://company.tracker.com?e=[EVENT_ID]") + validator(t, tc.args.bid, tc.want.tags) + }) + } +} + +func validator(t *testing.T, b *openrtb2.Bid, expectedTags []string) { + adm := b.AdM + assert.NotNil(t, adm) + assert.NotEmpty(t, adm) + // check tags are present + + for _, tag := range expectedTags { + assert.True(t, strings.Contains(adm, tag), "expected '"+tag+"' tag in Adm") + } +} diff --git a/exchange/exchange.go b/exchange/exchange.go index 211d82fc79c..43c86bc7f5a 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -83,6 +83,8 @@ type exchange struct { macroReplacer macros.Replacer priceFloorEnabled bool priceFloorFetcher floors.FloorFetcher + floor config.PriceFloors + trakerURL string } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -178,6 +180,8 @@ func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid macroReplacer: macroReplacer, priceFloorEnabled: cfg.PriceFloors.Enabled, priceFloorFetcher: priceFloorFetcher, + floor: cfg.PriceFloors, + trakerURL: cfg.TrackerURL, } } @@ -272,6 +276,10 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog var floorErrs []error if e.priceFloorEnabled { floorErrs = floors.EnrichWithPriceFloors(r.BidRequestWrapper, r.Account, conversions, e.priceFloorFetcher) + if floors.RequestHasFloors(r.BidRequestWrapper.BidRequest) { + // Record request count with non-zero imp.bidfloor value + e.me.RecordFloorsRequestForAccount(r.PubID) + } } responseDebugAllow, accountDebugAllow, debugLog := getDebugInfo(r.BidRequestWrapper.Test, requestExtPrebid, r.Account.DebugAllow, debugLog) @@ -374,7 +382,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog alternateBidderCodes = *r.Account.AlternateBidderCodes } var extraRespInfo extraAuctionResponseInfo - adapterBids, adapterExtra, extraRespInfo = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime, bidAdjustmentRules, r.TmaxAdjustments) + adapterBids, adapterExtra, extraRespInfo = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime, bidAdjustmentRules, r.Account.PriceFloors.AdjustForBidAdjustment, r.TmaxAdjustments) fledge = extraRespInfo.fledge anyBidsReturned = extraRespInfo.bidsFound r.BidderResponseStartTime = extraRespInfo.bidderResponseStartTime @@ -386,7 +394,6 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog bidResponseExt *openrtb_ext.ExtBidResponse seatNonBids = nonBids{} ) - if anyBidsReturned { if e.priceFloorEnabled { var rejectedBids []*entities.PbsOrtbSeatBid @@ -399,25 +406,49 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog Message: fmt.Sprintf("%s bid id %s rejected - bid price %.4f %s is less than bid floor %.4f %s for imp %s", rejectedBid.Seat, rejectedBid.Bids[0].Bid.ID, rejectedBid.Bids[0].Bid.Price, rejectedBid.Currency, rejectedBid.Bids[0].BidFloors.FloorValue, rejectedBid.Bids[0].BidFloors.FloorCurrency, rejectedBid.Bids[0].Bid.ImpID), WarningCode: errortypes.FloorBidRejectionWarningCode}) } + + if len(rejectedBids) > 0 { + // Record rejected bid count at account level + e.me.RecordRejectedBidsForAccount(r.PubID) + updateSeatNonBidsFloors(&seatNonBids, rejectedBids) + } + + if responseDebugAllow { + if err := r.BidRequestWrapper.RebuildRequestExt(); err != nil { + return nil, err + } + resolvedBidReq, err := json.Marshal(r.BidRequestWrapper.BidRequest) + if err != nil { + return nil, err + } + r.ResolvedBidRequest = resolvedBidReq + } } + adapterBids, rejections := applyAdvertiserBlocking(r, adapterBids, &seatNonBids) + // add advertiser blocking specific errors + for _, message := range rejections { + errs = append(errs, errors.New(message)) + } var bidCategory map[string]string //If includebrandcategory is present in ext then CE feature is on. if requestExtPrebid.Targeting != nil && requestExtPrebid.Targeting.IncludeBrandCategory != nil { var rejections []string - bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, *requestExtPrebid.Targeting, adapterBids, e.categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) + bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, r, *requestExtPrebid.Targeting, adapterBids, e.categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) if err != nil { return nil, fmt.Errorf("Error in category mapping : %s", err.Error()) } for _, message := range rejections { errs = append(errs, errors.New(message)) } + } if e.bidIDGenerator.Enabled() { for _, seatBid := range adapterBids { for _, pbsBid := range seatBid.Bids { pbsBid.GeneratedBidID, err = e.bidIDGenerator.New() + glog.Infof("Original BidID = %s Generated BidID = %s", pbsBid.Bid.ID, pbsBid.GeneratedBidID) if err != nil { errs = append(errs, errors.New("Error generating bid.ext.prebid.bidid")) } @@ -426,7 +457,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog } evTracking := getEventTracking(requestExtPrebid, r.StartTime, &r.Account, e.bidderInfo, e.externalURL) - adapterBids = evTracking.modifyBidsForEvents(adapterBids) + adapterBids = evTracking.modifyBidsForEvents(adapterBids, r.BidRequestWrapper.BidRequest, e.trakerURL) r.HookExecutor.ExecuteAllProcessedBidResponsesStage(adapterBids) @@ -491,6 +522,13 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral] = append(bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral], generalWarning) } + if enabled, rules := floorsEnabled(r.Account, r.BidRequestWrapper); enabled && rules != nil { + if bidResponseExt.Prebid == nil { + bidResponseExt.Prebid = &openrtb_ext.ExtResponsePrebid{} + } + bidResponseExt.Prebid.Floors = rules + } + e.bidValidationEnforcement.SetBannerCreativeMaxSize(r.Account.Validations) // Build the response @@ -671,7 +709,9 @@ func (e *exchange) getAllBids( hookExecutor hookexecution.StageExecutor, pbsRequestStartTime time.Time, bidAdjustmentRules map[string][]openrtb_ext.Adjustment, + bidFloorAdjustment bool, tmaxAdjustments *TmaxAdjustmentsPreprocessed) ( + map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, extraAuctionResponseInfo) { @@ -736,9 +776,15 @@ func (e *exchange) getAllBids( if seatBid != nil { for _, bid := range seatBid.Bids { var cpm = float64(bid.Bid.Price * 1000) + e.me.RecordAdapterPrice(bidderRequest.BidderLabels, cpm) e.me.RecordAdapterBidReceived(bidderRequest.BidderLabels, bid.BidType, bid.Bid.AdM != "") + if bid.BidType == openrtb_ext.BidTypeVideo && bid.BidVideo != nil && bid.BidVideo.Duration > 0 { + e.me.RecordAdapterVideoBidDuration(bidderRequest.BidderLabels, bid.BidVideo.Duration) + } } + // Setting bidderCoreName in SeatBid + seatBid.BidderCoreName = bidderRequest.BidderCoreName } } chBids <- brw @@ -769,9 +815,15 @@ func (e *exchange) getAllBids( } //but we need to add all bidders data to adapterExtra to have metrics and other metadata adapterExtra[brw.bidder] = brw.adapterExtra - - if !extraRespInfo.bidsFound && adapterBids[brw.bidder] != nil && len(adapterBids[brw.bidder].Bids) > 0 { + } + for _, adapterBid := range adapterBids { + if len(adapterBid.Bids) > 0 { extraRespInfo.bidsFound = true + bidIDsCollision := recordAdaptorDuplicateBidIDs(e.me, adapterBids) + if bidIDsCollision { + e.me.RecordRequestHavingDuplicateBidID() + } + break } } @@ -814,9 +866,19 @@ func (e *exchange) recoverSafely(bidderRequests []BidderRequest, allBidders = sb.String()[:sb.Len()-1] } + bidderRequestStr := "" + if nil != bidderRequest.BidRequest { + value, err := json.Marshal(bidderRequest.BidRequest) + if nil == err { + bidderRequestStr = string(value) + } else { + bidderRequestStr = err.Error() + } + } + glog.Errorf("OpenRTB auction recovered panic from Bidder %s: %v. "+ - "Account id: %s, All Bidders: %s, Stack trace is: %v", - bidderRequest.BidderCoreName, r, bidderRequest.BidderLabels.PubID, allBidders, string(debug.Stack())) + "Account id: %s, All Bidders: %s, BidRequest: %s, Stack trace is: %v", + bidderRequest.BidderCoreName, r, bidderRequest.BidderLabels.PubID, allBidders, bidderRequestStr, string(debug.Stack())) e.me.RecordAdapterPanic(bidderRequest.BidderLabels) // Let the master request know that there is no data here brw := new(bidResponseWrapper) @@ -925,7 +987,8 @@ func encodeBidResponseExt(bidResponseExt *openrtb_ext.ExtBidResponse) ([]byte, e return buffer.Bytes(), err } -func applyCategoryMapping(ctx context.Context, targeting openrtb_ext.ExtRequestTargeting, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator, seatNonBids *nonBids) (map[string]string, map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []string, error) { +func applyCategoryMapping(ctx context.Context, r *AuctionRequest, targeting openrtb_ext.ExtRequestTargeting, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator, seatNonBids *nonBids) (map[string]string, map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []string, error) { + bidRequest := r.BidRequestWrapper.BidRequest res := make(map[string]string) type bidDedupe struct { @@ -937,6 +1000,8 @@ func applyCategoryMapping(ctx context.Context, targeting openrtb_ext.ExtRequestT dedupe := make(map[string]bidDedupe) + impMap := make(map[string]*openrtb2.Imp) + // applyCategoryMapping doesn't get called unless brandCatExt := targeting.IncludeBrandCategory @@ -950,6 +1015,11 @@ func applyCategoryMapping(ctx context.Context, targeting openrtb_ext.ExtRequestT var rejections []string var translateCategories = true + //Maintaining BidRequest Impression Map + for i := range bidRequest.Imp { + impMap[bidRequest.Imp[i].ID] = &bidRequest.Imp[i] + } + if includeBrandCategory && brandCatExt.WithCategory { if brandCatExt.TranslateCategories != nil { translateCategories = *brandCatExt.TranslateCategories @@ -980,6 +1050,7 @@ func applyCategoryMapping(ctx context.Context, targeting openrtb_ext.ExtRequestT duration = bid.BidVideo.Duration category = bid.BidVideo.PrimaryCategory } + if brandCatExt.WithCategory && category == "" { bidIabCat := bid.Bid.Cat if len(bidIabCat) != 1 { @@ -997,6 +1068,7 @@ func applyCategoryMapping(ctx context.Context, targeting openrtb_ext.ExtRequestT //TODO: add metrics //if mapping required but no mapping file is found then discard the bid bidsToRemove = append(bidsToRemove, bidInd) + seatNonBids.addBid(bid, int(openrtb3.LossBidCategoryMapping), string(seatBid.Seat)) reason := fmt.Sprintf("Category mapping file for primary ad server: '%s', publisher: '%s' not found", primaryAdServer, publisher) rejections = updateRejections(rejections, bidID, reason) continue @@ -1014,10 +1086,21 @@ func applyCategoryMapping(ctx context.Context, targeting openrtb_ext.ExtRequestT newDur, err := findDurationRange(duration, targeting.DurationRangeSec) if err != nil { bidsToRemove = append(bidsToRemove, bidInd) + seatNonBids.addBid(bid, int(openrtb3.LossBidCategoryMapping), string(seatBid.Seat)) rejections = updateRejections(rejections, bidID, err.Error()) continue } + if len(targeting.DurationRangeSec) == 0 { + if targData.priceGranularity.Test || newDur == 0 { + if imp, ok := impMap[bid.Bid.ImpID]; ok { + if nil != imp.Video && imp.Video.MaxDuration > 0 { + newDur = int(imp.Video.MaxDuration) + } + } + } + } + var categoryDuration string var dupeKey string if brandCatExt.WithCategory { @@ -1032,50 +1115,54 @@ func applyCategoryMapping(ctx context.Context, targeting openrtb_ext.ExtRequestT categoryDuration = fmt.Sprintf("%s_%s", categoryDuration, bidderName.String()) } - if dupe, ok := dedupe[dupeKey]; ok { + if !brandCatExt.SkipDedup { + if dupe, ok := dedupe[dupeKey]; ok { - dupeBidPrice, err := strconv.ParseFloat(dupe.bidPrice, 64) - if err != nil { - dupeBidPrice = 0 - } - currBidPrice, err := strconv.ParseFloat(priceBucket, 64) - if err != nil { - currBidPrice = 0 - } - if dupeBidPrice == currBidPrice { - if booleanGenerator.Generate() { - dupeBidPrice = -1 - } else { - currBidPrice = -1 + dupeBidPrice, err := strconv.ParseFloat(dupe.bidPrice, 64) + if err != nil { + dupeBidPrice = 0 + } + currBidPrice, err := strconv.ParseFloat(priceBucket, 64) + if err != nil { + currBidPrice = 0 + } + if dupeBidPrice == currBidPrice { + if booleanGenerator.Generate() { + dupeBidPrice = -1 + } else { + currBidPrice = -1 + } } - } - if dupeBidPrice < currBidPrice { - if dupe.bidderName == bidderName { - // An older bid from the current bidder - bidsToRemove = append(bidsToRemove, dupe.bidIndex) - rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") - } else { - // An older bid from a different seatBid we've already finished with - oldSeatBid := (seatBids)[dupe.bidderName] - rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") - if len(oldSeatBid.Bids) == 1 { - seatBidsToRemove = append(seatBidsToRemove, dupe.bidderName) + if dupeBidPrice < currBidPrice { + if dupe.bidderName == bidderName { + // An older bid from the current bidder + bidsToRemove = append(bidsToRemove, dupe.bidIndex) + seatNonBids.addBid(bid, int(openrtb3.LossBidCategoryMapping), string(seatBid.Seat)) + rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") } else { - // This is a very rare, but still possible case where bid needs to be removed from already processed bidder - // This happens when current processing bidder has a bid that has same deduplication key as a bid from already processed bidder - // and already processed bid was selected to be removed - // See example of input data in unit test `TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice` - // Need to remove bid by name, not index in this case - removeBidById(oldSeatBid, dupe.bidID) + // An older bid from a different seatBid we've already finished with + oldSeatBid := (seatBids)[dupe.bidderName] + rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") + if len(oldSeatBid.Bids) == 1 { + seatBidsToRemove = append(seatBidsToRemove, dupe.bidderName) + } else { + // This is a very rare, but still possible case where bid needs to be removed from already processed bidder + // This happens when current processing bidder has a bid that has same deduplication key as a bid from already processed bidder + // and already processed bid was selected to be removed + // See example of input data in unit test `TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice` + // Need to remove bid by name, not index in this case + removeBidById(oldSeatBid, dupe.bidID) + } } + delete(res, dupe.bidID) + } else { + // Remove this bid + bidsToRemove = append(bidsToRemove, bidInd) + seatNonBids.addBid(bid, int(openrtb3.LossBidCategoryMapping), string(seatBid.Seat)) + rejections = updateRejections(rejections, bidID, "Bid was deduplicated") + continue } - delete(res, dupe.bidID) - } else { - // Remove this bid - bidsToRemove = append(bidsToRemove, bidInd) - rejections = updateRejections(rejections, bidID, "Bid was deduplicated") - continue } } res[bidID] = categoryDuration @@ -1280,7 +1367,7 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea } } - if bidExtJSON, err := makeBidExtJSON(bid.Bid.Ext, bidExtPrebid, impExtInfoMap, bid.Bid.ImpID, bid.OriginalBidCPM, bid.OriginalBidCur, adapter); err != nil { + if bidExtJSON, err := makeBidExtJSON(bid.Bid.Ext, bidExtPrebid, impExtInfoMap, bid.Bid.ImpID, bid.OriginalBidCPM, bid.OriginalBidCur, bid.OriginalBidCPMUSD, adapter); err != nil { errs = append(errs, err) } else { result = append(result, *bid.Bid) @@ -1294,7 +1381,7 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea return result, errs } -func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impExtInfoMap map[string]ImpExtInfo, impId string, originalBidCpm float64, originalBidCur string, adapter openrtb_ext.BidderName) (json.RawMessage, error) { +func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impExtInfoMap map[string]ImpExtInfo, impId string, originalBidCpm float64, originalBidCur string, originalBidCpmUSD float64, adapter openrtb_ext.BidderName) (json.RawMessage, error) { var extMap map[string]interface{} if len(ext) != 0 { @@ -1315,6 +1402,11 @@ func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impEx extMap[openrtb_ext.OriginalBidCurKey] = originalBidCur } + //ext.origbidcpmusd + if originalBidCpmUSD > float64(0) { + extMap[openrtb_ext.OriginalBidCpmUsdKey] = originalBidCpmUSD + } + // ext.prebid if prebid.Meta == nil && maputil.HasElement(extMap, "prebid", "meta") { metaContainer := struct { @@ -1476,9 +1568,12 @@ func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessag //create new seat bid and add it to live adapters liveAdapters = append(liveAdapters, bidderName) newSeatBid := entities.PbsOrtbSeatBid{ - Bids: bidsToAdd, - Currency: "", - Seat: "", + Bids: bidsToAdd, + Currency: "", + FledgeAuctionConfigs: nil, + HttpCalls: nil, + Seat: "", + BidderCoreName: bidderName, } adapterBids[bidderName] = &newSeatBid diff --git a/exchange/exchange_ow.go b/exchange/exchange_ow.go new file mode 100644 index 00000000000..df021ef0274 --- /dev/null +++ b/exchange/exchange_ow.go @@ -0,0 +1,268 @@ +package exchange + +import ( + "context" + "errors" + "fmt" + "net/url" + "regexp" + "strings" + + "github.com/golang/glog" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/metrics" + pubmaticstats "github.com/prebid/prebid-server/v2/metrics/pubmatic_stats" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/ortb" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "golang.org/x/net/publicsuffix" +) + +const ( + bidCountMetricEnabled = "bidCountMetricEnabled" + owProfileId = "owProfileId" + nodeal = "nodeal" + vastVersionUndefined = "undefined" +) + +const ( + VASTTypeWrapperEndTag = "" + VASTTypeInLineEndTag = "" +) + +// VASTTagType describes the allowed values for VASTTagType +type VASTTagType string + +const ( + WrapperVASTTagType VASTTagType = "Wrapper" + InLineVASTTagType VASTTagType = "InLine" + URLVASTTagType VASTTagType = "URL" + UnknownVASTTagType VASTTagType = "Unknown" +) + +var ( + vastVersionRegex = regexp.MustCompile(``) +) + +// recordAdaptorDuplicateBidIDs finds the bid.id collisions for each bidder and records them with metrics engine +// it returns true if collosion(s) is/are detected in any of the bidder's bids +func recordAdaptorDuplicateBidIDs(metricsEngine metrics.MetricsEngine, adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) bool { + bidIDCollisionFound := false + if nil == adapterBids { + return false + } + for bidder, bid := range adapterBids { + bidIDColisionMap := make(map[string]int, len(adapterBids[bidder].Bids)) + for _, thisBid := range bid.Bids { + if collisions, ok := bidIDColisionMap[thisBid.Bid.ID]; ok { + bidIDCollisionFound = true + bidIDColisionMap[thisBid.Bid.ID]++ + glog.Warningf("Bid.id %v :: %v collision(s) [imp.id = %v] for bidder '%v'", thisBid.Bid.ID, collisions, thisBid.Bid.ImpID, string(bidder)) + metricsEngine.RecordAdapterDuplicateBidID(string(bidder), 1) + } else { + bidIDColisionMap[thisBid.Bid.ID] = 1 + } + } + } + return bidIDCollisionFound +} + +// normalizeDomain validates, normalizes and returns valid domain or error if failed to validate +// checks if domain starts with http by lowercasing entire domain +// if not it prepends it before domain. This is required for obtaining the url +// using url.parse method. on successfull url parsing, it will replace first occurance of www. +// from the domain +func normalizeDomain(domain string) (string, error) { + domain = strings.Trim(strings.ToLower(domain), " ") + // not checking if it belongs to icann + suffix, _ := publicsuffix.PublicSuffix(domain) + if domain != "" && suffix == domain { // input is publicsuffix + return "", errors.New("domain [" + domain + "] is public suffix") + } + if !strings.HasPrefix(domain, "http") { + domain = fmt.Sprintf("http://%s", domain) + } + url, err := url.Parse(domain) + if nil == err && url.Host != "" { + return strings.Replace(url.Host, "www.", "", 1), nil + } + return "", err +} + +// applyAdvertiserBlocking rejects the bids of blocked advertisers mentioned in req.badv +// the rejection is currently only applicable to vast tag bidders. i.e. not for ortb bidders +// it returns seatbids containing valid bids and rejections containing rejected bid.id with reason +func applyAdvertiserBlocking(r *AuctionRequest, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, seatNonBids *nonBids) (map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []string) { + bidRequest := r.BidRequestWrapper.BidRequest + rejections := []string{} + nBadvs := []string{} + if nil != bidRequest.BAdv { + for _, domain := range bidRequest.BAdv { + nDomain, err := normalizeDomain(domain) + if nil == err && nDomain != "" { // skip empty and domains with errors + nBadvs = append(nBadvs, nDomain) + } + } + } + + if len(nBadvs) == 0 { + return seatBids, rejections + } + + for bidderName, seatBid := range seatBids { + if seatBid.BidderCoreName == openrtb_ext.BidderVASTBidder { + for bidIndex := len(seatBid.Bids) - 1; bidIndex >= 0; bidIndex-- { + bid := seatBid.Bids[bidIndex] + for _, bAdv := range nBadvs { + aDomains := bid.Bid.ADomain + rejectBid := false + if nil == aDomains { + // provision to enable rejecting of bids when req.badv is set + rejectBid = true + } else { + for _, d := range aDomains { + if aDomain, err := normalizeDomain(d); nil == err { + // compare and reject bid if + // 1. aDomain == bAdv + // 2. .bAdv is suffix of aDomain + // 3. aDomain not present but request has list of block advertisers + if aDomain == bAdv || strings.HasSuffix(aDomain, "."+bAdv) || (len(aDomain) == 0 && len(bAdv) > 0) { + // aDomain must be subdomain of bAdv + rejectBid = true + break + } + } + } + } + if rejectBid { + // Add rejected bid in seatNonBid. + seatNonBids.addBid(bid, int(openrtb3.LossBidAdvertiserBlocking), seatBid.Seat) + // reject the bid. bid belongs to blocked advertisers list + seatBid.Bids = append(seatBid.Bids[:bidIndex], seatBid.Bids[bidIndex+1:]...) + rejections = updateRejections(rejections, bid.Bid.ID, fmt.Sprintf("Bid (From '%s') belongs to blocked advertiser '%s'", bidderName, bAdv)) + break // bid is rejected due to advertiser blocked. No need to check further domains + } + } + } + } + } + return seatBids, rejections +} + +func recordBids(ctx context.Context, metricsEngine metrics.MetricsEngine, pubID string, adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) { + // Temporary code to record bids for publishers + if metricEnabled, ok := ctx.Value(bidCountMetricEnabled).(bool); metricEnabled && ok { + if profileID, ok := ctx.Value(owProfileId).(string); ok && profileID != "" { + for _, seatBid := range adapterBids { + for _, pbsBid := range seatBid.Bids { + deal := pbsBid.Bid.DealID + if deal == "" { + deal = nodeal + } + metricsEngine.RecordBids(pubID, profileID, seatBid.Seat, deal) + pubmaticstats.IncBidResponseByDealCountInPBS(pubID, profileID, seatBid.Seat, deal) + } + } + } + } +} + +func recordVastVersion(metricsEngine metrics.MetricsEngine, adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) { + for _, seatBid := range adapterBids { + for _, pbsBid := range seatBid.Bids { + if pbsBid.BidType != openrtb_ext.BidTypeVideo { + continue + } + if pbsBid.Bid.AdM == "" { + continue + } + vastVersion := vastVersionUndefined + matches := vastVersionRegex.FindStringSubmatch(pbsBid.Bid.AdM) + if len(matches) == 2 { + vastVersion = matches[1] + } + + metricsEngine.RecordVastVersion(string(seatBid.BidderCoreName), vastVersion) + } + } +} + +func recordVASTTagType(metricsEngine metrics.MetricsEngine, adapterBids *adapters.BidderResponse, bidder openrtb_ext.BidderName) { + for _, adapterBid := range adapterBids.Bids { + if adapterBid.BidType == openrtb_ext.BidTypeVideo { + vastTagType := UnknownVASTTagType + if index := strings.LastIndex(adapterBid.Bid.AdM, VASTTypeWrapperEndTag); index != -1 { + vastTagType = WrapperVASTTagType + } else if index := strings.LastIndex(adapterBid.Bid.AdM, VASTTypeInLineEndTag); index != -1 { + vastTagType = InLineVASTTagType + } else if IsUrl(adapterBid.Bid.AdM) { + vastTagType = URLVASTTagType + } + metricsEngine.RecordVASTTagType(string(bidder), string(vastTagType)) + } + } +} + +func IsUrl(adm string) bool { + url, err := url.Parse(adm) + return err == nil && url.Scheme != "" && url.Host != "" +} + +// recordPartnerTimeout captures the partnertimeout if any at publisher profile level +func recordPartnerTimeout(ctx context.Context, pubID, aliasBidder string) { + if metricEnabled, ok := ctx.Value(bidCountMetricEnabled).(bool); metricEnabled && ok { + if profileID, ok := ctx.Value(owProfileId).(string); ok && profileID != "" { + pubmaticstats.IncPartnerTimeoutInPBS(pubID, profileID, aliasBidder) + } + } +} + +// updateSeatNonBidsFloors updates seatnonbid with rejectedBids due to floors +func updateSeatNonBidsFloors(seatNonBids *nonBids, rejectedBids []*entities.PbsOrtbSeatBid) { + for _, pbsRejSeatBid := range rejectedBids { + for _, pbsRejBid := range pbsRejSeatBid.Bids { + var rejectionReason = openrtb3.LossBidBelowAuctionFloor + if pbsRejBid.Bid.DealID != "" { + rejectionReason = openrtb3.LossBidBelowDealFloor + } + seatNonBids.addBid(pbsRejBid, int(rejectionReason), pbsRejSeatBid.Seat) + } + } +} + +// GetPriceBucketOW is the externally facing function for computing CPM buckets +func GetPriceBucketOW(cpm float64, config openrtb_ext.PriceGranularity) string { + bid := openrtb2.Bid{ + Price: cpm, + } + newPG := setPriceGranularityOW(&config) + targetData := targetData{ + priceGranularity: *newPG, + mediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{}, + } + return GetPriceBucket(bid, targetData) +} + +func setPriceGranularityOW(pg *openrtb_ext.PriceGranularity) *openrtb_ext.PriceGranularity { + if pg == nil || len(pg.Ranges) == 0 { + pg = ptrutil.ToPtr(openrtb_ext.NewPriceGranularityDefault()) + return pg + } + + if pg.Precision == nil { + pg.Precision = ptrutil.ToPtr(ortb.DefaultPriceGranularityPrecision) + } + + var prevMax float64 = 0 + for i, r := range pg.Ranges { + if pg.Ranges[i].Min != prevMax { + pg.Ranges[i].Min = prevMax + } + prevMax = r.Max + } + + return pg +} diff --git a/exchange/exchange_ow_test.go b/exchange/exchange_ow_test.go new file mode 100644 index 00000000000..33660a2517f --- /dev/null +++ b/exchange/exchange_ow_test.go @@ -0,0 +1,1831 @@ +package exchange + +import ( + "context" + "encoding/json" + "fmt" + "math" + "regexp" + "sort" + "strings" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/vastbidder" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/metrics" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +// TestApplyAdvertiserBlocking verifies advertiser blocking +// Currently it is expected to work only with TagBidders and not woth +// normal bidders +func TestApplyAdvertiserBlocking(t *testing.T) { + type args struct { + advBlockReq *AuctionRequest + adaptorSeatBids map[*bidderAdapter]*entities.PbsOrtbSeatBid // bidder adaptor and its dummy seat bids map + } + type want struct { + rejectedBidIds []string + validBidCountPerSeat map[string]int + expectedseatNonBids nonBids + } + tests := []struct { + name string + args args + want want + }{ + { + name: "reject_bid_of_blocked_adv_from_tag_bidder", + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + BAdv: []string{"a.com"}, // block bids returned by a.com + }, + }, + }, + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("vast_tag_bidder"): { // tag bidder returning 1 bid from blocked advertiser + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "a.com_bid", + ADomain: []string{"a.com"}, + }, + }, + { + Bid: &openrtb2.Bid{ + ID: "b.com_bid", + ADomain: []string{"b.com"}, + }, + }, + { + Bid: &openrtb2.Bid{ + ID: "keep_ba.com", + ADomain: []string{"ba.com"}, + }, + }, + { + Bid: &openrtb2.Bid{ + ID: "keep_ba.com", + ADomain: []string{"b.a.com.shri.com"}, + }, + }, + { + Bid: &openrtb2.Bid{ + ID: "reject_b.a.com.a.com.b.c.d.a.com", + ADomain: []string{"b.a.com.a.com.b.c.d.a.com"}, + }, + }, + }, + BidderCoreName: openrtb_ext.BidderVASTBidder, + }, + }, + }, + want: want{ + expectedseatNonBids: nonBids{ + seatNonBidsMap: map[string][]openrtb_ext.NonBid{ + "": { + { + StatusCode: int(openrtb3.LossBidAdvertiserBlocking), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + ID: "reject_b.a.com.a.com.b.c.d.a.com", + ADomain: []string{"b.a.com.a.com.b.c.d.a.com"}, + }, + }, + }, + }, + { + StatusCode: int(openrtb3.LossBidAdvertiserBlocking), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + ID: "a.com_bid", + ADomain: []string{"a.com"}, + }, + }, + }, + }, + }, + }, + }, + rejectedBidIds: []string{"a.com_bid", "reject_b.a.com.a.com.b.c.d.a.com"}, + validBidCountPerSeat: map[string]int{ + "vast_tag_bidder": 3, + }, + }, + }, + { + name: "Badv_is_not_present", // expect no advertiser blocking + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: nil}, + }, + }, + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("tab_bidder_1"): { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ID: "bid_1_adapter_1", ADomain: []string{"a.com"}}}, + {Bid: &openrtb2.Bid{ID: "bid_2_adapter_1"}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{}, // no bid rejection expected + validBidCountPerSeat: map[string]int{ + "tab_bidder_1": 2, + }, + expectedseatNonBids: nonBids{}, + }, + }, + { + name: "adomain_is_not_present_but_Badv_is_set", // reject bids without adomain as badv is set + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: []string{"advertiser_1.com"}}, + }, + }, + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("tag_bidder_1"): { + Bids: []*entities.PbsOrtbBid{ // expect all bids are rejected + {Bid: &openrtb2.Bid{ID: "bid_1_adapter_1_without_adomain"}}, + {Bid: &openrtb2.Bid{ID: "bid_2_adapter_1_with_empty_adomain", ADomain: []string{"", " "}}}, + }, + }, + newTestRtbAdapter("rtb_bidder_1"): { + Bids: []*entities.PbsOrtbBid{ // all bids should be present. It belongs to RTB adapator + {Bid: &openrtb2.Bid{ID: "bid_1_adapter_2_without_adomain"}}, + {Bid: &openrtb2.Bid{ID: "bid_2_adapter_2_with_empty_adomain", ADomain: []string{"", " "}}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{"bid_1_adapter_1_without_adomain", "bid_2_adapter_1_with_empty_adomain"}, + validBidCountPerSeat: map[string]int{ + "tag_bidder_1": 0, // expect 0 bids. i.e. all bids are rejected + "rtb_bidder_1": 2, // no bid must be rejected + }, + expectedseatNonBids: nonBids{}, + }, + }, + { + name: "adomain_and_badv_is_not_present", // expect no advertiser blocking + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + }, + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("tag_adaptor_1"): { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ID: "bid_without_adomain"}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{}, // no rejection expected as badv not present + validBidCountPerSeat: map[string]int{ + "tag_adaptor_1": 1, + }, + expectedseatNonBids: nonBids{}, + }, + }, + { + name: "empty_badv", // expect no advertiser blocking + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: []string{}}, + }, + }, + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("tag_bidder_1"): { + Bids: []*entities.PbsOrtbBid{ // expect all bids are rejected + {Bid: &openrtb2.Bid{ID: "bid_1_adapter_1", ADomain: []string{"a.com"}}}, + {Bid: &openrtb2.Bid{ID: "bid_2_adapter_1"}}, + }, + }, + newTestRtbAdapter("rtb_bidder_1"): { + Bids: []*entities.PbsOrtbBid{ // all bids should be present. It belongs to RTB adapator + {Bid: &openrtb2.Bid{ID: "bid_1_adapter_2_without_adomain"}}, + {Bid: &openrtb2.Bid{ID: "bid_2_adapter_2_with_empty_adomain", ADomain: []string{"", " "}}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{}, // no rejections expect as there is not badv set + validBidCountPerSeat: map[string]int{ + "tag_bidder_1": 2, + "rtb_bidder_1": 2, + }, + expectedseatNonBids: nonBids{}, + }, + }, + { + name: "nil_badv", // expect no advertiser blocking + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: nil}, + }, + }, + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("tag_bidder_1"): { + Bids: []*entities.PbsOrtbBid{ // expect all bids are rejected + {Bid: &openrtb2.Bid{ID: "bid_1_adapter_1", ADomain: []string{"a.com"}}}, + {Bid: &openrtb2.Bid{ID: "bid_2_adapter_1"}}, + }, + }, + newTestRtbAdapter("rtb_bidder_1"): { + Bids: []*entities.PbsOrtbBid{ // all bids should be present. It belongs to RTB adapator + {Bid: &openrtb2.Bid{ID: "bid_1_adapter_2_without_adomain"}}, + {Bid: &openrtb2.Bid{ID: "bid_2_adapter_2_with_empty_adomain", ADomain: []string{"", " "}}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{}, // no rejections expect as there is not badv set + validBidCountPerSeat: map[string]int{ + "tag_bidder_1": 2, + "rtb_bidder_1": 2, + }, + expectedseatNonBids: nonBids{}, + }, + }, + { + name: "ad_domains_normalized_and_checked", + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: []string{"a.com"}}, + }, + }, + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("my_adapter"): { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ID: "bid_1_of_blocked_adv", ADomain: []string{"www.a.com"}}}, + // expect a.com is extracted from page url + {Bid: &openrtb2.Bid{ID: "bid_2_of_blocked_adv", ADomain: []string{"http://a.com/my/page?k1=v1&k2=v2"}}}, + // invalid adomain - will be skipped and the bid will be not be rejected + {Bid: &openrtb2.Bid{ID: "bid_3_with_domain_abcd1234", ADomain: []string{"abcd1234"}}}, + }, + }}, + }, + want: want{ + rejectedBidIds: []string{"bid_1_of_blocked_adv", "bid_2_of_blocked_adv"}, + validBidCountPerSeat: map[string]int{"my_adapter": 1}, + expectedseatNonBids: nonBids{}, + }, + }, { + name: "multiple_badv", + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: []string{"advertiser_1.com", "advertiser_2.com", "www.advertiser_3.com"}}, + }, + }, + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("tag_adapter_1"): { + Bids: []*entities.PbsOrtbBid{ + // adomain without www prefix + {Bid: &openrtb2.Bid{ID: "bid_1_tag_adapter_1", ADomain: []string{"advertiser_3.com"}}}, + {Bid: &openrtb2.Bid{ID: "bid_2_tag_adapter_1", ADomain: []string{"advertiser_2.com"}}}, + {Bid: &openrtb2.Bid{ID: "bid_3_tag_adapter_1", ADomain: []string{"advertiser_4.com"}}}, + {Bid: &openrtb2.Bid{ID: "bid_4_tag_adapter_1", ADomain: []string{"advertiser_100.com"}}}, + }, + }, + newTestTagAdapter("tag_adapter_2"): { + Bids: []*entities.PbsOrtbBid{ + // adomain has www prefix + {Bid: &openrtb2.Bid{ID: "bid_1_tag_adapter_2", ADomain: []string{"www.advertiser_1.com"}}}, + }, + }, + newTestRtbAdapter("rtb_adapter_1"): { + Bids: []*entities.PbsOrtbBid{ + // should not reject following bid though its advertiser is blocked + // because this bid belongs to RTB Adaptor + {Bid: &openrtb2.Bid{ID: "bid_1_rtb_adapter_2", ADomain: []string{"advertiser_1.com"}}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{"bid_1_tag_adapter_1", "bid_2_tag_adapter_1", "bid_1_tag_adapter_2"}, + validBidCountPerSeat: map[string]int{ + "tag_adapter_1": 2, + "tag_adapter_2": 0, + "rtb_adapter_1": 1, + }, + expectedseatNonBids: nonBids{}, + }, + }, { + name: "multiple_adomain", + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: []string{"www.advertiser_3.com"}}, + }, + }, + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("tag_adapter_1"): { + Bids: []*entities.PbsOrtbBid{ + // adomain without www prefix + {Bid: &openrtb2.Bid{ID: "bid_1_tag_adapter_1", ADomain: []string{"a.com", "b.com", "advertiser_3.com", "d.com"}}}, + {Bid: &openrtb2.Bid{ID: "bid_2_tag_adapter_1", ADomain: []string{"a.com", "https://advertiser_3.com"}}}, + {Bid: &openrtb2.Bid{ID: "bid_3_tag_adapter_1", ADomain: []string{"advertiser_4.com"}}}, + {Bid: &openrtb2.Bid{ID: "bid_4_tag_adapter_1", ADomain: []string{"advertiser_100.com"}}}, + }, + }, + newTestTagAdapter("tag_adapter_2"): { + Bids: []*entities.PbsOrtbBid{ + // adomain has www prefix + {Bid: &openrtb2.Bid{ID: "bid_1_tag_adapter_2", ADomain: []string{"a.com", "b.com", "www.advertiser_3.com"}}}, + }, + }, + newTestRtbAdapter("rtb_adapter_1"): { + Bids: []*entities.PbsOrtbBid{ + // should not reject following bid though its advertiser is blocked + // because this bid belongs to RTB Adaptor + {Bid: &openrtb2.Bid{ID: "bid_1_rtb_adapter_2", ADomain: []string{"a.com", "b.com", "advertiser_3.com"}}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{"bid_1_tag_adapter_1", "bid_2_tag_adapter_1", "bid_1_tag_adapter_2"}, + validBidCountPerSeat: map[string]int{ + "tag_adapter_1": 2, + "tag_adapter_2": 0, + "rtb_adapter_1": 1, + }, + expectedseatNonBids: nonBids{}, + }, + }, { + name: "case_insensitive_badv", // case of domain not matters + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: []string{"ADVERTISER_1.COM"}}, + }, + }, + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("tag_adapter_1"): { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ID: "bid_1_rtb_adapter_1", ADomain: []string{"advertiser_1.com"}}}, + {Bid: &openrtb2.Bid{ID: "bid_2_rtb_adapter_1", ADomain: []string{"www.advertiser_1.com"}}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{"bid_1_rtb_adapter_1", "bid_2_rtb_adapter_1"}, + validBidCountPerSeat: map[string]int{ + "tag_adapter_1": 0, // expect all bids are rejected as belongs to blocked advertiser + }, + expectedseatNonBids: nonBids{}, + }, + }, + { + name: "case_insensitive_adomain", + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: []string{"advertiser_1.com"}}, + }, + }, + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("tag_adapter_1"): { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ID: "bid_1_rtb_adapter_1", ADomain: []string{"advertiser_1.COM"}}}, + {Bid: &openrtb2.Bid{ID: "bid_2_rtb_adapter_1", ADomain: []string{"wWw.ADVERTISER_1.com"}}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{"bid_1_rtb_adapter_1", "bid_2_rtb_adapter_1"}, + validBidCountPerSeat: map[string]int{ + "tag_adapter_1": 0, // expect all bids are rejected as belongs to blocked advertiser + }, + expectedseatNonBids: nonBids{}, + }, + }, + { + name: "various_tld_combinations", + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: []string{"http://blockme.shri"}}, + }, + }, + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("block_bidder"): { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ADomain: []string{"www.blockme.shri"}, ID: "reject_www.blockme.shri"}}, + {Bid: &openrtb2.Bid{ADomain: []string{"http://www.blockme.shri"}, ID: "rejecthttp://www.blockme.shri"}}, + {Bid: &openrtb2.Bid{ADomain: []string{"https://blockme.shri"}, ID: "reject_https://blockme.shri"}}, + {Bid: &openrtb2.Bid{ADomain: []string{"https://www.blockme.shri"}, ID: "reject_https://www.blockme.shri"}}, + }, + }, + newTestRtbAdapter("rtb_non_block_bidder"): { + Bids: []*entities.PbsOrtbBid{ // all below bids are eligible and should not be rejected + {Bid: &openrtb2.Bid{ADomain: []string{"www.blockme.shri"}, ID: "accept_bid_www.blockme.shri"}}, + {Bid: &openrtb2.Bid{ADomain: []string{"http://www.blockme.shri"}, ID: "accept_bid__http://www.blockme.shri"}}, + {Bid: &openrtb2.Bid{ADomain: []string{"https://blockme.shri"}, ID: "accept_bid__https://blockme.shri"}}, + {Bid: &openrtb2.Bid{ADomain: []string{"https://www.blockme.shri"}, ID: "accept_bid__https://www.blockme.shri"}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{"reject_www.blockme.shri", "reject_http://www.blockme.shri", "reject_https://blockme.shri", "reject_https://www.blockme.shri"}, + validBidCountPerSeat: map[string]int{ + "block_bidder": 0, + "rtb_non_block_bidder": 4, + }, + expectedseatNonBids: nonBids{}, + }, + }, + { + name: "subdomain_tests", + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: []string{"10th.college.puneunv.edu"}}, + }, + }, + + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("block_bidder"): { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ADomain: []string{"shri.10th.college.puneunv.edu"}, ID: "reject_shri.10th.college.puneunv.edu"}}, + {Bid: &openrtb2.Bid{ADomain: []string{"puneunv.edu"}, ID: "allow_puneunv.edu"}}, + {Bid: &openrtb2.Bid{ADomain: []string{"http://WWW.123.456.10th.college.PUNEUNV.edu"}, ID: "reject_123.456.10th.college.puneunv.edu"}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{"reject_shri.10th.college.puneunv.edu", "reject_123.456.10th.college.puneunv.edu"}, + validBidCountPerSeat: map[string]int{ + "block_bidder": 1, + }, + expectedseatNonBids: nonBids{}, + }, + }, { + name: "only_domain_test", // do not expect bid rejection. edu is valid domain + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: []string{"edu"}}, + }, + }, + + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("tag_bidder"): { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ADomain: []string{"school.edu"}, ID: "keep_bid_school.edu"}}, + {Bid: &openrtb2.Bid{ADomain: []string{"edu"}, ID: "keep_bid_edu"}}, + {Bid: &openrtb2.Bid{ADomain: []string{"..edu"}, ID: "keep_bid_..edu"}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{}, + validBidCountPerSeat: map[string]int{ + "tag_bidder": 3, + }, + expectedseatNonBids: nonBids{}, + }, + }, + { + name: "public_suffix_in_badv", + args: args{ + advBlockReq: &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{BAdv: []string{"co.in"}}, + }, + }, + // co.in is valid public suffix + adaptorSeatBids: map[*bidderAdapter]*entities.PbsOrtbSeatBid{ + newTestTagAdapter("tag_bidder"): { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ADomain: []string{"a.co.in"}, ID: "allow_a.co.in"}}, + {Bid: &openrtb2.Bid{ADomain: []string{"b.com"}, ID: "allow_b.com"}}, + }, + }, + }, + }, + want: want{ + rejectedBidIds: []string{}, + validBidCountPerSeat: map[string]int{ + "tag_bidder": 2, + }, + expectedseatNonBids: nonBids{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name != "reject_bid_of_blocked_adv_from_tag_bidder" { + return + } + seatBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) + tagBidders := make(map[openrtb_ext.BidderName]adapters.Bidder) + adapterMap := make(map[openrtb_ext.BidderName]AdaptedBidder, 0) + for adaptor, sbids := range tt.args.adaptorSeatBids { + adapterMap[adaptor.BidderName] = adaptor + if tagBidder, ok := adaptor.Bidder.(*vastbidder.TagBidder); ok { + tagBidders[adaptor.BidderName] = tagBidder + } + seatBids[adaptor.BidderName] = sbids + } + + seatNonBids := nonBids{} + // applyAdvertiserBlocking internally uses tagBidders from (adapter_map.go) + // not testing alias here + seatBids, rejections := applyAdvertiserBlocking(tt.args.advBlockReq, seatBids, &seatNonBids) + re := regexp.MustCompile("bid rejected \\[bid ID:(.*?)\\] reason") + for bidder, sBid := range seatBids { + // verify only eligible bids are returned + assert.Equal(t, tt.want.validBidCountPerSeat[string(bidder)], len(sBid.Bids), "Expected eligible bids are %d, but found [%d] ", tt.want.validBidCountPerSeat[string(bidder)], len(sBid.Bids)) + // verify rejections + assert.Equal(t, len(tt.want.rejectedBidIds), len(rejections), "Expected bid rejections are %d, but found [%d]", len(tt.want.rejectedBidIds), len(rejections)) + // verify rejected bid ids + present := false + for _, expectRejectedBidID := range tt.want.rejectedBidIds { + for _, rejection := range rejections { + match := re.FindStringSubmatch(rejection) + rejectedBidID := strings.Trim(match[1], " ") + if expectRejectedBidID == rejectedBidID { + present = true + break + } + } + if present { + break + } + } + if len(tt.want.rejectedBidIds) > 0 && !present { + assert.Fail(t, "Expected Bid ID [%s] as rejected. But bid is not rejected", re) + } + + if sBid.BidderCoreName != openrtb_ext.BidderVASTBidder { + continue // advertiser blocking is currently enabled only for tag bidders + } + + sort.Slice(seatNonBids.seatNonBidsMap[sBid.Seat], func(i, j int) bool { + return seatNonBids.seatNonBidsMap[sBid.Seat][i].Ext.Prebid.Bid.ID > seatNonBids.seatNonBidsMap[sBid.Seat][j].Ext.Prebid.Bid.ID + }) + sort.Slice(tt.want.expectedseatNonBids.seatNonBidsMap[sBid.Seat], func(i, j int) bool { + return tt.want.expectedseatNonBids.seatNonBidsMap[sBid.Seat][i].Ext.Prebid.Bid.ID > tt.want.expectedseatNonBids.seatNonBidsMap[sBid.Seat][j].Ext.Prebid.Bid.ID + }) + assert.Equal(t, tt.want.expectedseatNonBids.seatNonBidsMap, seatNonBids.seatNonBidsMap, "SeatNonBids not matching") + + for _, bid := range sBid.Bids { + if nil != bid.Bid.ADomain { + for _, adomain := range bid.Bid.ADomain { + for _, blockDomain := range tt.args.advBlockReq.BidRequestWrapper.BidRequest.BAdv { + nDomain, _ := normalizeDomain(adomain) + if nDomain == blockDomain { + assert.Fail(t, "bid %s with ad domain %s is not blocked", bid.Bid.ID, adomain) + } + } + } + } + + // verify this bid not belongs to rejected list + for _, rejectedBidID := range tt.want.rejectedBidIds { + if rejectedBidID == bid.Bid.ID { + assert.Fail(t, "Bid ID [%s] is not expected in list of rejected bids", bid.Bid.ID) + } + } + } + } + }) + } +} + +func TestNormalizeDomain(t *testing.T) { + type args struct { + domain string + } + type want struct { + domain string + err error + } + tests := []struct { + name string + args args + want want + }{ + {name: "a.com", args: args{domain: "a.com"}, want: want{domain: "a.com"}}, + {name: "http://a.com", args: args{domain: "http://a.com"}, want: want{domain: "a.com"}}, + {name: "https://a.com", args: args{domain: "https://a.com"}, want: want{domain: "a.com"}}, + {name: "https://www.a.com", args: args{domain: "https://www.a.com"}, want: want{domain: "a.com"}}, + {name: "https://www.a.com/my/page?k=1", args: args{domain: "https://www.a.com/my/page?k=1"}, want: want{domain: "a.com"}}, + {name: "empty_domain", args: args{domain: ""}, want: want{domain: ""}}, + {name: "trim_domain", args: args{domain: " trim.me?k=v "}, want: want{domain: "trim.me"}}, + {name: "trim_domain_with_http_in_it", args: args{domain: " http://trim.me?k=v "}, want: want{domain: "trim.me"}}, + {name: "https://www.something.a.com/my/page?k=1", args: args{domain: "https://www.something.a.com/my/page?k=1"}, want: want{domain: "something.a.com"}}, + {name: "wWW.something.a.com", args: args{domain: "wWW.something.a.com"}, want: want{domain: "something.a.com"}}, + {name: "2_times_www", args: args{domain: "www.something.www.a.com"}, want: want{domain: "something.www.a.com"}}, + {name: "consecutive_www", args: args{domain: "www.www.something.a.com"}, want: want{domain: "www.something.a.com"}}, + {name: "abchttp.com", args: args{domain: "abchttp.com"}, want: want{domain: "abchttp.com"}}, + {name: "HTTP://CAPS.com", args: args{domain: "HTTP://CAPS.com"}, want: want{domain: "caps.com"}}, + + // publicsuffix + {name: "co.in", args: args{domain: "co.in"}, want: want{domain: "", err: fmt.Errorf("domain [co.in] is public suffix")}}, + {name: ".co.in", args: args{domain: ".co.in"}, want: want{domain: ".co.in"}}, + {name: "amazon.co.in", args: args{domain: "amazon.co.in"}, want: want{domain: "amazon.co.in"}}, + // we wont check if shriprasad belongs to icann + {name: "shriprasad", args: args{domain: "shriprasad"}, want: want{domain: "", err: fmt.Errorf("domain [shriprasad] is public suffix")}}, + {name: ".shriprasad", args: args{domain: ".shriprasad"}, want: want{domain: ".shriprasad"}}, + {name: "abc.shriprasad", args: args{domain: "abc.shriprasad"}, want: want{domain: "abc.shriprasad"}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + adjustedDomain, err := normalizeDomain(tt.args.domain) + actualErr := "nil" + expectedErr := "nil" + if nil != err { + actualErr = err.Error() + } + if nil != tt.want.err { + actualErr = tt.want.err.Error() + } + assert.Equal(t, tt.want.err, err, "Expected error is %s, but found [%s]", expectedErr, actualErr) + assert.Equal(t, tt.want.domain, adjustedDomain, "Expected domain is %s, but found [%s]", tt.want.domain, adjustedDomain) + }) + } +} + +func newTestTagAdapter(name string) *bidderAdapter { + return &bidderAdapter{ + Bidder: vastbidder.NewTagBidder(openrtb_ext.BidderName(name), config.Adapter{}), + BidderName: openrtb_ext.BidderName(name), + } +} + +func newTestRtbAdapter(name string) *bidderAdapter { + return &bidderAdapter{ + Bidder: &goodSingleBidder{}, + BidderName: openrtb_ext.BidderName(name), + } +} + +func TestRecordAdaptorDuplicateBidIDs(t *testing.T) { + type bidderCollisions = map[string]int + testCases := []struct { + scenario string + bidderCollisions *bidderCollisions // represents no of collisions detected for bid.id at bidder level for given request + hasCollision bool + }{ + {scenario: "invalid collision value", bidderCollisions: &map[string]int{"bidder-1": -1}, hasCollision: false}, + {scenario: "no collision", bidderCollisions: &map[string]int{"bidder-1": 0}, hasCollision: false}, + {scenario: "one collision", bidderCollisions: &map[string]int{"bidder-1": 1}, hasCollision: false}, + {scenario: "multiple collisions", bidderCollisions: &map[string]int{"bidder-1": 2}, hasCollision: true}, // when 2 collisions it counter will be 1 + {scenario: "multiple bidders", bidderCollisions: &map[string]int{"bidder-1": 2, "bidder-2": 4}, hasCollision: true}, + {scenario: "multiple bidders with bidder-1 no collision", bidderCollisions: &map[string]int{"bidder-1": 1, "bidder-2": 4}, hasCollision: true}, + {scenario: "no bidders", bidderCollisions: nil, hasCollision: false}, + } + testEngine := metricsConf.NewMetricsEngine(&config.Configuration{}, metricsConf.NewMetricsRegistry(), nil, nil, nil) + + for _, testcase := range testCases { + var adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid + if nil == testcase.bidderCollisions { + break + } + adapterBids = make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) + for bidder, collisions := range *testcase.bidderCollisions { + bids := make([]*entities.PbsOrtbBid, 0) + testBidID := "bid_id_for_bidder_" + bidder + // add bids as per collisions value + bidCount := 0 + for ; bidCount < collisions; bidCount++ { + bids = append(bids, &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + ID: testBidID, + }, + }) + } + if nil == adapterBids[openrtb_ext.BidderName(bidder)] { + adapterBids[openrtb_ext.BidderName(bidder)] = new(entities.PbsOrtbSeatBid) + } + adapterBids[openrtb_ext.BidderName(bidder)].Bids = bids + } + assert.Equal(t, testcase.hasCollision, recordAdaptorDuplicateBidIDs(testEngine, adapterBids)) + } +} + +func TestMakeBidExtJSONOW(t *testing.T) { + + type aTest struct { + description string + ext json.RawMessage + extBidPrebid openrtb_ext.ExtBidPrebid + impExtInfo map[string]ImpExtInfo + origbidcpm float64 + origbidcur string + origbidcpmusd float64 + expectedBidExt string + expectedErrMessage string + } + + testCases := []aTest{ + { + description: "Valid extension with origbidcpmusd = 0", + ext: json.RawMessage(`{"video":{"h":100}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video"), Meta: &openrtb_ext.ExtBidPrebidMeta{BrandName: "foo"}, Passthrough: nil}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, + origbidcpm: 10.0000, + origbidcur: "USD", + expectedBidExt: `{"prebid":{"meta": {"adaptercode":"adapter","brandName": "foo"}, "passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedErrMessage: "", + }, + { + description: "Valid extension with origbidcpmusd > 0", + ext: json.RawMessage(`{"video":{"h":100}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video"), Meta: &openrtb_ext.ExtBidPrebidMeta{BrandName: "foo"}, Passthrough: nil}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, + origbidcpm: 10.0000, + origbidcur: "USD", + origbidcpmusd: 10.0000, + expectedBidExt: `{"prebid":{"meta": {"adaptercode":"adapter","brandName": "foo"}, "passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD", "origbidcpmusd": 10}`, + expectedErrMessage: "", + }, + } + + for _, test := range testCases { + var adapter openrtb_ext.BidderName = "adapter" + result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id", test.origbidcpm, test.origbidcur, test.origbidcpmusd, adapter) + + if test.expectedErrMessage == "" { + assert.JSONEq(t, test.expectedBidExt, string(result), "Incorrect result") + assert.NoError(t, err, "Error should not be returned") + } else { + assert.Contains(t, err.Error(), test.expectedErrMessage, "incorrect error message") + } + } +} + +func TestCallRecordBids(t *testing.T) { + + type args struct { + ctx context.Context + pubID string + adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid + getMetricsEngine func() *metrics.MetricsEngineMock + } + + tests := []struct { + name string + args args + }{ + { + name: "empty context", + args: args{ + ctx: context.Background(), + pubID: "1010", + getMetricsEngine: func() *metrics.MetricsEngineMock { + return &metrics.MetricsEngineMock{} + }, + }, + }, + { + name: "bidCountMetricEnabled is false", + args: args{ + ctx: context.WithValue(context.Background(), bidCountMetricEnabled, false), + pubID: "1010", + getMetricsEngine: func() *metrics.MetricsEngineMock { + return &metrics.MetricsEngineMock{} + }, + }, + }, + { + name: "bidCountMetricEnabled is true, owProfileId is non-string", + args: args{ + ctx: context.WithValue(context.WithValue(context.Background(), bidCountMetricEnabled, true), owProfileId, 1), + pubID: "1010", + getMetricsEngine: func() *metrics.MetricsEngineMock { + return &metrics.MetricsEngineMock{} + }, + }, + }, + { + name: "bidCountMetricEnabled is true, owProfileId is empty", + args: args{ + ctx: context.WithValue(context.WithValue(context.Background(), bidCountMetricEnabled, true), owProfileId, ""), + pubID: "1010", + getMetricsEngine: func() *metrics.MetricsEngineMock { + return &metrics.MetricsEngineMock{} + }, + }, + }, + { + name: "empty adapterBids", + args: args{ + ctx: context.WithValue(context.WithValue(context.Background(), bidCountMetricEnabled, true), owProfileId, "11"), + pubID: "1010", + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{}, + getMetricsEngine: func() *metrics.MetricsEngineMock { + return &metrics.MetricsEngineMock{} + }, + }, + }, + { + name: "empty adapterBids.seat", + args: args{ + ctx: context.WithValue(context.WithValue(context.Background(), bidCountMetricEnabled, true), owProfileId, "11"), + pubID: "1010", + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": {}, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + return &metrics.MetricsEngineMock{} + }, + }, + }, + { + name: "empty adapterBids.seat.bids", + args: args{ + ctx: context.WithValue(context.WithValue(context.Background(), bidCountMetricEnabled, true), owProfileId, "11"), + pubID: "1010", + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{}, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + return &metrics.MetricsEngineMock{} + }, + }, + }, + { + name: "multiple non deal bid", + args: args{ + ctx: context.WithValue(context.WithValue(context.Background(), bidCountMetricEnabled, true), owProfileId, "11"), + pubID: "1010", + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + }, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + }, + }, + }, + Seat: "pubmatic", + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordBids", "1010", "11", "pubmatic", nodeal).Return() + metricEngine.Mock.On("RecordBids", "1010", "11", "pubmatic", nodeal).Return() + return metricEngine + }, + }, + }, + { + name: "multiple deal bid", + args: args{ + ctx: context.WithValue(context.WithValue(context.Background(), bidCountMetricEnabled, true), owProfileId, "11"), + pubID: "1010", + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + DealID: "pubdeal", + }, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + DealID: "pubdeal", + }, + }, + }, + Seat: "pubmatic", + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordBids", "1010", "11", "pubmatic", "pubdeal").Return() + metricEngine.Mock.On("RecordBids", "1010", "11", "pubmatic", "pubdeal").Return() + return metricEngine + }, + }, + }, + { + name: "multiple bidders", + args: args{ + ctx: context.WithValue(context.WithValue(context.Background(), bidCountMetricEnabled, true), owProfileId, "11"), + pubID: "1010", + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + DealID: "pubdeal", + }, + }, + }, + Seat: "pubmatic", + }, + "appnexus": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid2", + DealID: "appnxdeal", + }, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid3", + }, + }, + }, + Seat: "appnexus", + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordBids", "1010", "11", "pubmatic", "pubdeal").Return() + metricEngine.Mock.On("RecordBids", "1010", "11", "appnexus", "appnxdeal").Return() + metricEngine.Mock.On("RecordBids", "1010", "11", "appnexus", nodeal).Return() + return metricEngine + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockMetricEngine := tt.args.getMetricsEngine() + recordBids(tt.args.ctx, mockMetricEngine, tt.args.pubID, tt.args.adapterBids) + mockMetricEngine.AssertExpectations(t) + }) + } +} + +func TestRecordVastVersion(t *testing.T) { + type args struct { + metricsEngine metrics.MetricsEngine + adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid + getMetricsEngine func() *metrics.MetricsEngineMock + } + tests := []struct { + name string + args args + }{ + { + name: "No Bids", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{}, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + return metricEngine + }, + }, + }, + { + name: "Empty Bids in SeatBid", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{}, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + return metricEngine + }, + }, + }, + { + name: "Empty Bids in SeatBid", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{}, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + return metricEngine + }, + }, + }, + { + name: "Invalid Bid Type", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + BidType: openrtb_ext.BidTypeBanner, + }, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + return metricEngine + }, + }, + }, + { + name: "No Adm in Bids", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + AdM: "", + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + return metricEngine + }, + }, + }, + { + name: "No version found in Adm", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + BidderCoreName: "pubmatic", + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + AdM: " ", + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordVastVersion", "pubmatic", vastVersionUndefined).Return() + return metricEngine + }, + }, + }, + { + name: "Version found in Adm", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + BidderCoreName: "pubmatic", + Bids: []*entities.PbsOrtbBid{ + { + BidType: openrtb_ext.BidTypeVideo, + Bid: &openrtb2.Bid{ + AdM: ` +    +      +       Adsystem Example +       VAST 2.0 +       VAST 2.0 +       http://myErrorURL/error +       http://myTrackingURL/impression +        +          +            +            00:00:30 +              +               http://myTrackingURL/creativeView +               http://myTrackingURL/start +               http://myTrackingURL/midpoint +               http://myTrackingURL/firstQuartile +               http://myTrackingURL/thirdQuartile +               http://myTrackingURL/complete +              +              +               http://www.examplemedia.com +               http://myTrackingURL/click +              +              +               +         http://demo.examplemedia.com/video/acudeo/Carrot_400x300_500kb.flv +            +           +            +      +      +        +             +               +              http://demo.examplemedia.com/vast/this_is_the_ad.jpg +               +               +                http://myTrackingURL/tracking +               +            http://www.examplemedia.com +             +             +               +              http://demo.examplemedia.com/vast/trackingbanner +               +            http://www.examplemedia.com +             +           +         +       +     +     + `, + }, + }, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordVastVersion", "pubmatic", "2.0").Return() + return metricEngine + }, + }, + }, + { + name: "Version found in Adm with spaces in tag", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + BidderCoreName: "pubmatic", + Bids: []*entities.PbsOrtbBid{ + { + BidType: openrtb_ext.BidTypeVideo, + Bid: &openrtb2.Bid{ + AdM: ` + `, + }, + }, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordVastVersion", "pubmatic", "4.1").Return() + return metricEngine + }, + }, + }, + { + name: "Version found in Adm with multiple attributes", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + BidderCoreName: "pubmatic", + Bids: []*entities.PbsOrtbBid{ + { + BidType: openrtb_ext.BidTypeVideo, + Bid: &openrtb2.Bid{ + AdM: ` + `, + }, + }, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordVastVersion", "pubmatic", "2.0").Return() + return metricEngine + }, + }, + }, + { + name: "Version found xml tag before Vast tag attributes", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + BidderCoreName: "pubmatic", + Bids: []*entities.PbsOrtbBid{ + { + BidType: openrtb_ext.BidTypeVideo, + Bid: &openrtb2.Bid{ + AdM: ` + `, + }, + }, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordVastVersion", "pubmatic", "2.0").Return() + return metricEngine + }, + }, + }, + { + name: "Version found in Adm inside single quote", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + BidderCoreName: "pubmatic", + Bids: []*entities.PbsOrtbBid{ + { + BidType: openrtb_ext.BidTypeVideo, + Bid: &openrtb2.Bid{ + AdM: ` + `, + }, + }, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordVastVersion", "pubmatic", "2.0").Return() + return metricEngine + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockMetricEngine := tt.args.getMetricsEngine() + recordVastVersion(mockMetricEngine, tt.args.adapterBids) + mockMetricEngine.AssertExpectations(t) + }) + } +} + +func TestGetPriceBucketStringOW(t *testing.T) { + low, _ := openrtb_ext.NewPriceGranularityFromLegacyID("low") + medium, _ := openrtb_ext.NewPriceGranularityFromLegacyID("medium") + high, _ := openrtb_ext.NewPriceGranularityFromLegacyID("high") + auto, _ := openrtb_ext.NewPriceGranularityFromLegacyID("auto") + dense, _ := openrtb_ext.NewPriceGranularityFromLegacyID("dense") + testPG, _ := openrtb_ext.NewPriceGranularityFromLegacyID("testpg") + custom1 := openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{ + { + Min: 0.0, + Max: 5.0, + Increment: 0.03, + }, + { + Min: 5.0, + Max: 10.0, + Increment: 0.1, + }, + }, + } + + custom2 := openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{ + { + Min: 0.0, + Max: 1.5, + Increment: 1.0, + }, + { + Min: 1.5, + Max: 10.0, + Increment: 1.2, + }, + }, + } + + // Define test cases + type aTest struct { + granularityId string + granularity openrtb_ext.PriceGranularity + expectedPriceBucket string + } + testGroups := []struct { + groupDesc string + cpm float64 + testCases []aTest + }{ + { + groupDesc: "cpm below the max in every price bucket", + cpm: 1.87, + testCases: []aTest{ + {"low", low, "1.50"}, + {"medium", medium, "1.80"}, + {"high", high, "1.87"}, + {"auto", auto, "1.85"}, + {"dense", dense, "1.87"}, + {"testpg", testPG, "50.00"}, + {"custom1", custom1, "1.86"}, + {"custom2", custom2, "1.50"}, + }, + }, + { + groupDesc: "cpm above the max in low price bucket", + cpm: 5.72, + testCases: []aTest{ + {"low", low, "5.00"}, + {"medium", medium, "5.70"}, + {"high", high, "5.72"}, + {"auto", auto, "5.70"}, + {"dense", dense, "5.70"}, + {"testpg", testPG, "50.00"}, + {"custom1", custom1, "5.70"}, + {"custom2", custom2, "5.10"}, + }, + }, + { + groupDesc: "cpm equal the max for custom granularity", + cpm: 10, + testCases: []aTest{ + {"custom1", custom1, "10.00"}, + {"custom2", custom2, "9.90"}, + }, + }, + { + groupDesc: "Precision value corner cases", + cpm: 1.876, + testCases: []aTest{ + { + "Negative precision defaults to number of digits already in CPM float", + openrtb_ext.PriceGranularity{Precision: ptrutil.ToPtr(-1), Ranges: []openrtb_ext.GranularityRange{{Max: 5, Increment: 0.05}}}, + "1.85", + }, + { + "Precision value equals zero, we expect to round up to the nearest integer", + openrtb_ext.PriceGranularity{Precision: ptrutil.ToPtr(0), Ranges: []openrtb_ext.GranularityRange{{Max: 5, Increment: 0.05}}}, + "2", + }, + { + "Largest precision value PBS supports 15", + openrtb_ext.PriceGranularity{Precision: ptrutil.ToPtr(15), Ranges: []openrtb_ext.GranularityRange{{Max: 5, Increment: 0.05}}}, + "1.850000000000000", + }, + }, + }, + { + groupDesc: "Increment value corner cases", + cpm: 1.876, + testCases: []aTest{ + { + "Negative increment, return empty string", + openrtb_ext.PriceGranularity{Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{{Max: 5, Increment: -0.05}}}, + "", + }, + { + "Zero increment, return empty string", + openrtb_ext.PriceGranularity{Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{{Max: 5}}}, + "", + }, + { + "Increment value is greater than CPM itself, return zero float value", + openrtb_ext.PriceGranularity{Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{{Max: 5, Increment: 1.877}}}, + "0.00", + }, + }, + }, + { + groupDesc: "Negative Cpm, return empty string since it does not belong into any range", + cpm: -1.876, + testCases: []aTest{{"low", low, ""}}, + }, + { + groupDesc: "Zero value Cpm, return the same, only in string format", + cpm: 0, + testCases: []aTest{{"low", low, "0.00"}}, + }, + { + groupDesc: "Large Cpm, return bucket Max", + cpm: math.MaxFloat64, + testCases: []aTest{{"low", low, "5.00"}}, + }, + { + groupDesc: "cpm above max test price granularity value", + cpm: 60, + testCases: []aTest{ + {"testpg", testPG, "50.00"}, + }, + }, + } + + for _, testGroup := range testGroups { + for i, test := range testGroup.testCases { + var priceBucket string + assert.NotPanics(t, func() { priceBucket = GetPriceBucketOW(testGroup.cpm, test.granularity) }, "Group: %s Granularity: %d", testGroup.groupDesc, i) + assert.Equal(t, test.expectedPriceBucket, priceBucket, "Group: %s Granularity: %s :: Expected %s, got %s from %f", testGroup.groupDesc, test.granularityId, test.expectedPriceBucket, priceBucket, testGroup.cpm) + } + } +} + +func Test_updateSeatNonBidsFloors(t *testing.T) { + type args struct { + seatNonBids *nonBids + rejectedBids []*entities.PbsOrtbSeatBid + } + tests := []struct { + name string + args args + expectedseatNonBids *nonBids + }{ + { + name: "nil rejectedBids", + args: args{ + rejectedBids: nil, + seatNonBids: &nonBids{}, + }, + expectedseatNonBids: &nonBids{}, + }, + { + name: "floors one rejectedBids in seatnonbid", + args: args{ + rejectedBids: []*entities.PbsOrtbSeatBid{ + { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + }, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + DealID: "deal1", + }, + }, + }, + Seat: "pubmatic", + }, + }, + seatNonBids: &nonBids{}, + }, + expectedseatNonBids: &nonBids{ + seatNonBidsMap: map[string][]openrtb_ext.NonBid{ + "pubmatic": { + { + StatusCode: 301, + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + ID: "bid1", + }, + }, + }, + }, + { + StatusCode: 304, + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + ID: "bid2", + DealID: "deal1", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "floors two rejectedBids in seatnonbid", + args: args{ + rejectedBids: []*entities.PbsOrtbSeatBid{ + { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + }, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + DealID: "deal1", + }, + }, + }, + Seat: "pubmatic", + }, + { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + }, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + DealID: "deal1", + }, + }, + }, + Seat: "appnexus", + }, + }, + seatNonBids: &nonBids{}, + }, + expectedseatNonBids: &nonBids{ + seatNonBidsMap: map[string][]openrtb_ext.NonBid{ + "pubmatic": { + { + StatusCode: 301, + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + ID: "bid1", + }, + }, + }, + }, + { + StatusCode: 304, + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + ID: "bid2", + DealID: "deal1", + }, + }, + }, + }, + }, + "appnexus": { + { + StatusCode: 301, + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + ID: "bid1", + }, + }, + }, + }, + { + StatusCode: 304, + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + ID: "bid2", + DealID: "deal1", + }, + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + updateSeatNonBidsFloors(tt.args.seatNonBids, tt.args.rejectedBids) + assert.Equal(t, tt.expectedseatNonBids, tt.args.seatNonBids) + }) + } +} + +func TestRecordVASTTagType(t *testing.T) { + var vastXMLAdM = "" + var inlineXMLAdM = "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1" + var URLAdM = "http://pubmatic.com" + type args struct { + metricsEngine metrics.MetricsEngine + adapterBids *adapters.BidderResponse + getMetricsEngine func() *metrics.MetricsEngineMock + } + tests := []struct { + name string + args args + }{ + { + name: "no_bids", + args: args{ + adapterBids: &adapters.BidderResponse{}, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + return metricEngine + }, + }, + }, + { + name: "empty_bids_in_seatbids", + args: args{ + adapterBids: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{}, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + return metricEngine + }, + }, + }, + { + name: "empty_adm", + args: args{ + adapterBids: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + AdM: "", + }, + Seat: "pubmatic", + BidType: openrtb_ext.BidTypeVideo, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordVASTTagType", "pubmatic", "Unknown").Return() + return metricEngine + }, + }, + }, + { + name: "adm_has_wrapped_xml", + args: args{ + adapterBids: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + AdM: vastXMLAdM, + }, + Seat: "pubmatic", + BidType: openrtb_ext.BidTypeVideo, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordVASTTagType", "pubmatic", "Wrapper").Return() + return metricEngine + }, + }, + }, + { + name: "adm_has_inline_xml", + args: args{ + adapterBids: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + AdM: inlineXMLAdM, + }, + Seat: "pubmatic", + BidType: openrtb_ext.BidTypeVideo, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordVASTTagType", "pubmatic", "InLine").Return() + return metricEngine + }, + }, + }, + { + name: "adm_has_url", + args: args{ + adapterBids: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + AdM: URLAdM, + }, + Seat: "pubmatic", + BidType: openrtb_ext.BidTypeVideo, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordVASTTagType", "pubmatic", "URL").Return() + return metricEngine + }, + }, + }, + { + name: "adm_has_wrapper_inline_url_adm", + args: args{ + adapterBids: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + AdM: vastXMLAdM, + }, + Seat: "pubmatic", + BidType: openrtb_ext.BidTypeVideo, + }, + { + Bid: &openrtb2.Bid{ + AdM: inlineXMLAdM, + }, + Seat: "pubmatic", + BidType: openrtb_ext.BidTypeVideo, + }, + { + Bid: &openrtb2.Bid{ + AdM: URLAdM, + }, + Seat: "pubmatic", + BidType: openrtb_ext.BidTypeVideo, + }, + }, + }, + getMetricsEngine: func() *metrics.MetricsEngineMock { + metricEngine := &metrics.MetricsEngineMock{} + metricEngine.Mock.On("RecordVASTTagType", "pubmatic", "Wrapper").Return() + metricEngine.Mock.On("RecordVASTTagType", "pubmatic", "InLine").Return() + metricEngine.Mock.On("RecordVASTTagType", "pubmatic", "URL").Return() + return metricEngine + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockMetricEngine := tt.args.getMetricsEngine() + recordVASTTagType(mockMetricEngine, tt.args.adapterBids, "pubmatic") + mockMetricEngine.AssertExpectations(t) + }) + } +} + +func TestIsUrl(t *testing.T) { + type args struct { + adm string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "empty_url", + args: args{ + adm: "", + }, + want: false, + }, + { + name: "valid_url", + args: args{ + adm: "http://www.test.com", + }, + want: true, + }, + { + name: "invalid_url_without_protocol", + args: args{ + adm: "//www.test.com/vast.xml", + }, + want: false, + }, + { + name: "invalid_url_without_host", + args: args{ + adm: "http://", + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsUrl(tt.args.adm); got != tt.want { + t.Errorf("IsUrl() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index cc5c7ac0871..6699708a39b 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -20,6 +20,7 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/currency" @@ -99,16 +100,16 @@ func TestNewExchange(t *testing.T) { // and check whether the returned request successfully prints any '&' characters as it should // To do so, we: // 1. Write the endpoint adapter URL with an '&' character into a new config,Configuration struct -// as specified in https://github.com/prebid/prebid-server/issues/465 +// as specified in https://github.com/prebid/prebid-server/v2/issues/465 // 2. Initialize a new exchange with said configuration // 3. Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs including the -// sample request as specified in https://github.com/prebid/prebid-server/issues/465 +// sample request as specified in https://github.com/prebid/prebid-server/v2/issues/465 // 4. Build a BidResponse struct using exchange.buildBidResponse(ctx.Background(), liveA... ) // 5. Assert we have no '&' characters in the response that exchange.buildBidResponse returns func TestCharacterEscape(t *testing.T) { // 1) Adapter with a '& char in its endpoint property - // https://github.com/prebid/prebid-server/issues/465 + // https://github.com/prebid/prebid-server/v2/issues/465 cfg := &config.Configuration{} biddersInfo := config.BidderInfos{"appnexus": config.BidderInfo{Endpoint: "http://ib.adnxs.com/openrtb2?query1&query2"}} //Note the '&' character in there @@ -143,7 +144,7 @@ func TestCharacterEscape(t *testing.T) { adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, 1) adapterBids["appnexus"] = &entities.PbsOrtbSeatBid{Currency: "USD"} - //An openrtb2.BidRequest struct as specified in https://github.com/prebid/prebid-server/issues/465 + //An openrtb2.BidRequest struct as specified in https://github.com/prebid/prebid-server/v2/issues/465 bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", Imp: []openrtb2.Imp{{ @@ -534,6 +535,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { openrtb_ext.BidderTelaria: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder2DebugEnabled}, ""), } // Run test + outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog) // Assert no HoldAuction err assert.NoErrorf(t, err, "ex.HoldAuction returned an err") @@ -800,6 +802,7 @@ func TestAdapterCurrency(t *testing.T) { HookExecutor: &hookexecution.EmptyHookExecutor{}, TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), } + response, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) assert.NoError(t, err) assert.Equal(t, "some-request-id", response.ID, "Response ID") @@ -1159,6 +1162,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) { // Run test debugLog := DebugLog{} + outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog) // Assert return error, if any @@ -1208,7 +1212,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { adapterList := make([]openrtb_ext.BidderName, 0, 2) syncerKeys := []string{} var moduleStageNames map[string][]string - testEngine := metricsConf.NewMetricsEngine(cfg, adapterList, syncerKeys, moduleStageNames) + testEngine := metricsConf.NewMetricsEngine(cfg, metricsConf.NewMetricsRegistry(), adapterList, syncerKeys, moduleStageNames) // 2) Init new exchange with said configuration handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) @@ -2036,6 +2040,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), } debugLog := DebugLog{} + _, err = e.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) @@ -2410,7 +2415,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] } } - metricsEngine := metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames(), nil, nil) + metricsEngine := metricsConf.NewMetricsEngine(&config.Configuration{}, metricsConf.NewMetricsRegistry(), openrtb_ext.CoreBidderNames(), nil, nil) requestSplitter := requestSplitter{ bidderToSyncerKey: bidderToSyncerKey, me: metricsEngine, @@ -2532,6 +2537,11 @@ func TestCategoryMapping(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } + r := &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + } requestExt := newExtRequest() targData := &targetData{ @@ -2552,10 +2562,10 @@ func TestCategoryMapping(t *testing.T) { bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, nil, 0, false, "", 30.0000, "USD", ""} - bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 40.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} + bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", "", 20.0000} + bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, nil, 0, false, "", 30.0000, "USD", "", 30.0000} + bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 40.0000, "USD", "", 40.0000} innerBids := []*entities.PbsOrtbBid{ &bid1_1, @@ -2568,8 +2578,37 @@ func TestCategoryMapping(t *testing.T) { bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid - - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + expectedseatNonBids := nonBids{ + seatNonBidsMap: map[string][]openrtb_ext.NonBid{ + "": { + { + ImpId: "imp_id4", + StatusCode: int(openrtb3.LossBidCategoryMapping), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 40.0000, + Cat: cats4, + W: 1, + H: 1, + OriginalBidCPM: 40, + OriginalBidCur: "USD", + OriginalBidCPMUSD: 40, + + ID: "bid_id4", + Type: openrtb_ext.BidTypeVideo, + Video: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 30, + }, + }, + }, + }, + }, + }, + }, + } + seatNonBids := nonBids{} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -2579,6 +2618,7 @@ func TestCategoryMapping(t *testing.T) { assert.Equal(t, "20.00_AdapterOverride_30s", bidCategory["bid_id3"], "Category mapping override from adapter didn't take") assert.Equal(t, 3, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match") assert.Equal(t, 3, len(bidCategory), "Bidders category mapping doesn't match") + assert.Equal(t, expectedseatNonBids, seatNonBids, "SeatNonBids for analytics don't match") } func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { @@ -2588,6 +2628,11 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } + r := &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + } requestExt := newExtRequestNoBrandCat() targData := &targetData{ @@ -2607,10 +2652,10 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, nil, 0, false, "", 30.0000, "USD", ""} - bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, nil, 0, false, "", 40.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} + bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", "", 20.0000} + bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, nil, 0, false, "", 30.0000, "USD", "", 30.0000} + bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, nil, 0, false, "", 40.0000, "USD", "", 40.0000} innerBids := []*entities.PbsOrtbBid{ &bid1_1, @@ -2623,8 +2668,9 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid - - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + expectedseatNonBids := nonBids{} + seatNonBids := nonBids{} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -2634,6 +2680,7 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { assert.Equal(t, "20.00_50s", bidCategory["bid_id4"], "Category mapping doesn't match") assert.Equal(t, 4, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match") assert.Equal(t, 4, len(bidCategory), "Bidders category mapping doesn't match") + assert.Equal(t, expectedseatNonBids, seatNonBids, "SeatNonBids not matching") } func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { @@ -2643,6 +2690,12 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } + r := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + } + requestExt := newExtRequestTranslateCategories(nil) targData := &targetData{ @@ -2661,9 +2714,9 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 30.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} + bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", "", 20.0000} + bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 30.0000, "USD", "", 30.0000} innerBids := []*entities.PbsOrtbBid{ &bid1_1, @@ -2676,7 +2729,38 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + expectedseatNonBids := nonBids{ + seatNonBidsMap: map[string][]openrtb_ext.NonBid{ + "": { + { + ImpId: "imp_id3", + StatusCode: int(openrtb3.LossBidCategoryMapping), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 30.0000, + Cat: cats3, + W: 1, + H: 1, + OriginalBidCPM: 30, + OriginalBidCur: "USD", + OriginalBidCPMUSD: 30, + + ID: "bid_id3", + Type: openrtb_ext.BidTypeVideo, + Video: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 30, + }, + }, + }, + }, + }, + }, + }, + } + + seatNonBids := nonBids{} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -2685,6 +2769,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { assert.Equal(t, "20.00_Sports_50s", bidCategory["bid_id2"], "Category mapping doesn't match") assert.Equal(t, 2, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match") assert.Equal(t, 2, len(bidCategory), "Bidders category mapping doesn't match") + assert.Equal(t, expectedseatNonBids, seatNonBids, "SeatNonBids not matching") } func newExtRequestTranslateCategories(translateCategories *bool) openrtb_ext.ExtRequest { @@ -2725,6 +2810,12 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { } translateCategories := false + + r := &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + } requestExt := newExtRequestTranslateCategories(&translateCategories) targData := &targetData{ @@ -2743,9 +2834,9 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 30.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} + bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", "", 20.0000} + bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 30.0000, "USD", "", 30.0000} innerBids := []*entities.PbsOrtbBid{ &bid1_1, @@ -2757,8 +2848,9 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid - - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + expectedseatNonBids := nonBids{} + seatNonbids := nonBids{} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonbids) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -2767,6 +2859,8 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { assert.Equal(t, "20.00_IAB1-1000_30s", bidCategory["bid_id3"], "Bid should not be rejected") assert.Equal(t, 3, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match") assert.Equal(t, 3, len(bidCategory), "Bidders category mapping doesn't match") + assert.Equal(t, expectedseatNonBids, seatNonbids, "SeatNonBids not matching") + } func TestCategoryDedupe(t *testing.T) { @@ -2774,6 +2868,11 @@ func TestCategoryDedupe(t *testing.T) { if error != nil { t.Errorf("Failed to create a category Fetcher: %v", error) } + r := &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + } requestExt := newExtRequest() targData := &targetData{ priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity, @@ -2836,7 +2935,7 @@ func TestCategoryDedupe(t *testing.T) { }, } deduplicateGenerator := fakeRandomDeduplicateBidBooleanGenerator{returnValue: tt.dedupeGeneratorValue} - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &deduplicateGenerator, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &deduplicateGenerator, &nonBids{}) assert.Nil(t, err) assert.Equal(t, 3, len(rejections)) @@ -2853,6 +2952,11 @@ func TestNoCategoryDedupe(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } + r := &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + } requestExt := newExtRequestNoBrandCat() targData := &targetData{ @@ -2871,11 +2975,11 @@ func TestNoCategoryDedupe(t *testing.T) { bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 14.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 14.0000, "USD", ""} - bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_5 := entities.PbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 14.0000, "USD", "", 14.0000} + bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 14.0000, "USD", "", 14.0000} + bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", "", 20.0000} + bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", "", 20.0000} + bid1_5 := entities.PbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} selectedBids := make(map[string]int) expectedCategories := map[string]string{ @@ -2905,7 +3009,7 @@ func TestNoCategoryDedupe(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages") @@ -2942,6 +3046,11 @@ func TestCategoryMappingBidderName(t *testing.T) { includeWinners: true, } + r := &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + } requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30} adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) @@ -2951,8 +3060,8 @@ func TestCategoryMappingBidderName(t *testing.T) { bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 10.0000, Cat: cats2, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} + bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} innerBids1 := []*entities.PbsOrtbBid{ &bid1_1, @@ -2970,7 +3079,9 @@ func TestCategoryMappingBidderName(t *testing.T) { adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + expectedseatNonBids := nonBids{} + seatNonBids := nonBids{} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) assert.NoError(t, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be 0 bid rejection messages") @@ -2979,6 +3090,7 @@ func TestCategoryMappingBidderName(t *testing.T) { assert.Len(t, adapterBids[bidderName1].Bids, 1, "Bidders number doesn't match") assert.Len(t, adapterBids[bidderName2].Bids, 1, "Bidders number doesn't match") assert.Len(t, bidCategory, 2, "Bidders category mapping doesn't match") + assert.Equal(t, expectedseatNonBids, seatNonBids, "SeatNonBids don't match") } func TestCategoryMappingBidderNameNoCategories(t *testing.T) { @@ -2996,6 +3108,11 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { includeWinners: true, } + r := &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + } requestExt.Prebid.Targeting.DurationRangeSec = []int{30, 10, 25, 5, 20, 50} adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) @@ -3005,8 +3122,8 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 12.0000, Cat: cats2, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 17}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 8}, nil, nil, 0, false, "", 12.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 17}, nil, nil, 0, false, "", 10.0000, "USD", "", 10} + bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 8}, nil, nil, 0, false, "", 12.0000, "USD", "", 12} innerBids1 := []*entities.PbsOrtbBid{ &bid1_1, @@ -3024,7 +3141,9 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + expectedseatNonBids := nonBids{} + seatNonBids := nonBids{} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) assert.NoError(t, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be 0 bid rejection messages") @@ -3033,6 +3152,7 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { assert.Len(t, adapterBids[bidderName1].Bids, 1, "Bidders number doesn't match") assert.Len(t, adapterBids[bidderName2].Bids, 1, "Bidders number doesn't match") assert.Len(t, bidCategory, 2, "Bidders category mapping doesn't match") + assert.Equal(t, expectedseatNonBids, seatNonBids, "SeatNonBids don't match") } func TestBidRejectionErrors(t *testing.T) { @@ -3058,12 +3178,13 @@ func TestBidRejectionErrors(t *testing.T) { bidderName := openrtb_ext.BidderName("appnexus") testCases := []struct { - description string - reqExt openrtb_ext.ExtRequest - bids []*openrtb2.Bid - duration int - expectedRejections []string - expectedCatDur string + description string + reqExt openrtb_ext.ExtRequest + bids []*openrtb2.Bid + duration int + expectedRejections []string + expectedCatDur string + expectedseatNonBids nonBids }{ { description: "Bid should be rejected due to not containing a category", @@ -3075,6 +3196,35 @@ func TestBidRejectionErrors(t *testing.T) { expectedRejections: []string{ "bid rejected [bid ID: bid_id1] reason: Bid did not contain a category", }, + expectedseatNonBids: nonBids{ + seatNonBidsMap: map[string][]openrtb_ext.NonBid{ + "appnexus": { + { + ImpId: "imp_id1", + StatusCode: int(openrtb3.LossBidCategoryMapping), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10.0000, + Cat: []string{}, + W: 1, + H: 1, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + OriginalBidCPMUSD: 10, + + ID: "bid_id1", + Type: openrtb_ext.BidTypeVideo, + Video: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 30, + }, + }, + }, + }, + }, + }, + }, + }, }, { description: "Bid should be rejected due to missing category mapping file", @@ -3086,6 +3236,35 @@ func TestBidRejectionErrors(t *testing.T) { expectedRejections: []string{ "bid rejected [bid ID: bid_id1] reason: Category mapping file for primary ad server: 'dfp', publisher: 'some_publisher' not found", }, + expectedseatNonBids: nonBids{ + seatNonBidsMap: map[string][]openrtb_ext.NonBid{ + "": { + { + ImpId: "imp_id1", + StatusCode: int(openrtb3.LossBidCategoryMapping), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10.0000, + Cat: []string{"IAB1-1"}, + W: 1, + H: 1, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + OriginalBidCPMUSD: 10, + + ID: "bid_id1", + Type: openrtb_ext.BidTypeVideo, + Video: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 30, + }, + }, + }, + }, + }, + }, + }, + }, }, { description: "Bid should be rejected due to duration exceeding maximum", @@ -3097,6 +3276,35 @@ func TestBidRejectionErrors(t *testing.T) { expectedRejections: []string{ "bid rejected [bid ID: bid_id1] reason: bid duration exceeds maximum allowed", }, + expectedseatNonBids: nonBids{ + seatNonBidsMap: map[string][]openrtb_ext.NonBid{ + "": { + { + ImpId: "imp_id1", + StatusCode: int(openrtb3.LossBidCategoryMapping), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10.0000, + Cat: []string{"IAB1-1"}, + W: 1, + H: 1, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + OriginalBidCPMUSD: 10, + + ID: "bid_id1", + Type: openrtb_ext.BidTypeVideo, + Video: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 70, + }, + }, + }, + }, + }, + }, + }, + }, }, { description: "Bid should be rejected due to duplicate bid", @@ -3110,6 +3318,35 @@ func TestBidRejectionErrors(t *testing.T) { "bid rejected [bid ID: bid_id1] reason: Bid was deduplicated", }, expectedCatDur: "10.00_VideoGames_30s", + expectedseatNonBids: nonBids{ + seatNonBidsMap: map[string][]openrtb_ext.NonBid{ + "": { + { + ImpId: "imp_id1", + StatusCode: int(openrtb3.LossBidCategoryMapping), + Ext: openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10.0000, + Cat: []string{"IAB1-1"}, + W: 1, + H: 1, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + OriginalBidCPMUSD: 10, + + ID: "bid_id1", + Type: openrtb_ext.BidTypeVideo, + Video: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 30, + }, + }, + }, + }, + }, + }, + }, + }, }, } @@ -3117,15 +3354,20 @@ func TestBidRejectionErrors(t *testing.T) { innerBids := []*entities.PbsOrtbBid{} for _, bid := range test.bids { currentBid := entities.PbsOrtbBid{ - bid, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, nil, 0, false, "", 10.0000, "USD", ""} + bid, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} innerBids = append(innerBids, ¤tBid) } seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"} adapterBids[bidderName] = &seatBid - - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *test.reqExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + r := &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + } + seatNonBids := nonBids{} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *test.reqExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) if len(test.expectedCatDur) > 0 { // Bid deduplication case @@ -3139,6 +3381,7 @@ func TestBidRejectionErrors(t *testing.T) { assert.Empty(t, err, "Category mapping error should be empty") assert.Equal(t, test.expectedRejections, rejections, test.description) + assert.Equal(t, test.expectedseatNonBids, seatNonBids, "Rejected Bids did not match for %v", test.description) } } @@ -3149,6 +3392,12 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } + r := &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + } + requestExt := newExtRequestTranslateCategories(nil) targData := &targetData{ @@ -3165,8 +3414,8 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { bidApn1 := openrtb2.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bidApn2 := openrtb2.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1} - bid1_Apn1 := entities.PbsOrtbBid{&bidApn1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_Apn2 := entities.PbsOrtbBid{&bidApn2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} + bid1_Apn1 := entities.PbsOrtbBid{&bidApn1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} + bid1_Apn2 := entities.PbsOrtbBid{&bidApn2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} innerBidsApn1 := []*entities.PbsOrtbBid{ &bid1_Apn1, @@ -3188,7 +3437,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - bidCategory, _, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) assert.NoError(t, err, "Category mapping error should be empty") assert.Len(t, rejections, 1, "There should be 1 bid rejection message") @@ -3226,6 +3475,11 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) t.Errorf("Failed to create a category Fetcher: %v", error) } + r := &AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + } requestExt := newExtRequestTranslateCategories(nil) targData := &targetData{ @@ -3245,11 +3499,11 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) bidApn2_1 := openrtb2.Bid{ID: "bid_idApn2_1", ImpID: "imp_idApn2_1", Price: 10.0000, Cat: cats2, W: 1, H: 1} bidApn2_2 := openrtb2.Bid{ID: "bid_idApn2_2", ImpID: "imp_idApn2_2", Price: 20.0000, Cat: cats2, W: 1, H: 1} - bid1_Apn1_1 := entities.PbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_Apn1_2 := entities.PbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} + bid1_Apn1_1 := entities.PbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} + bid1_Apn1_2 := entities.PbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", "", 20.0000} - bid1_Apn2_1 := entities.PbsOrtbBid{&bidApn2_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_Apn2_2 := entities.PbsOrtbBid{&bidApn2_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} + bid1_Apn2_1 := entities.PbsOrtbBid{&bidApn2_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} + bid1_Apn2_2 := entities.PbsOrtbBid{&bidApn2_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", "", 20.0000} innerBidsApn1 := []*entities.PbsOrtbBid{ &bid1_Apn1_1, @@ -3272,7 +3526,9 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - _, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &fakeRandomDeduplicateBidBooleanGenerator{true}, &nonBids{}) + expectedseatNonBids := nonBids{} + seatNonbids := nonBids{} + _, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &fakeRandomDeduplicateBidBooleanGenerator{true}, &seatNonbids) assert.NoError(t, err, "Category mapping error should be empty") @@ -3296,7 +3552,7 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) assert.Equal(t, 2, totalNumberOfbids, "2 bids total should be returned") assert.Len(t, rejections, 2, "2 bids should be de-duplicated") - + assert.Equal(t, expectedseatNonBids, seatNonbids, "Bid Rejections not matching") if firstBidderIndicator { assert.Len(t, adapterBids[bidderNameApn1].Bids, 2) assert.Len(t, adapterBids[bidderNameApn2].Bids, 0) @@ -3327,9 +3583,9 @@ func TestRemoveBidById(t *testing.T) { bidApn1_2 := openrtb2.Bid{ID: "bid_idApn1_2", ImpID: "imp_idApn1_2", Price: 20.0000, Cat: cats1, W: 1, H: 1} bidApn1_3 := openrtb2.Bid{ID: "bid_idApn1_3", ImpID: "imp_idApn1_3", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid1_Apn1_1 := entities.PbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_Apn1_2 := entities.PbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_Apn1_3 := entities.PbsOrtbBid{&bidApn1_3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} + bid1_Apn1_1 := entities.PbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} + bid1_Apn1_2 := entities.PbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", "", 20.0000} + bid1_Apn1_3 := entities.PbsOrtbBid{&bidApn1_3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", "", 10.0000} type aTest struct { desc string @@ -3530,7 +3786,7 @@ func TestApplyDealSupport(t *testing.T) { }, } - bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.in.dealPriority, false, "", 0, "USD", ""} + bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.in.dealPriority, false, "", 0, "USD", "", 0} bidCategory := map[string]string{ bid.Bid.ID: test.in.targ["hb_pb_cat_dur"], } @@ -3590,8 +3846,8 @@ func TestApplyDealSupportMultiBid(t *testing.T) { winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp_id1": { openrtb_ext.BidderName("appnexus"): { - &entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""}, - &entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""}, + &entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", "", 0}, + &entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", "", 0}, }, }, }, @@ -3636,8 +3892,8 @@ func TestApplyDealSupportMultiBid(t *testing.T) { winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp_id1": { openrtb_ext.BidderName("appnexus"): { - &entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""}, - &entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""}, + &entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", "", 0}, + &entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", "", 0}, }, }, }, @@ -3687,8 +3943,8 @@ func TestApplyDealSupportMultiBid(t *testing.T) { winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp_id1": { openrtb_ext.BidderName("appnexus"): { - &entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""}, - &entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""}, + &entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", "", 0}, + &entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", "", 0}, }, }, }, @@ -3879,7 +4135,7 @@ func TestUpdateHbPbCatDur(t *testing.T) { } for _, test := range testCases { - bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.dealPriority, false, "", 0, "USD", ""} + bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.dealPriority, false, "", 0, "USD", "", 0} bidCategory := map[string]string{ bid.Bid.ID: test.targ["hb_pb_cat_dur"], } @@ -3900,6 +4156,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo map[string]ImpExtInfo origbidcpm float64 origbidcur string + origbidcpmusd float64 expectedBidExt string expectedErrMessage string } @@ -4074,8 +4331,7 @@ func TestMakeBidExtJSON(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { var adapter openrtb_ext.BidderName = "adapter" - result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id", test.origbidcpm, test.origbidcur, adapter) - + result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id", test.origbidcpm, test.origbidcur, test.origbidcpmusd, adapter) if test.expectedErrMessage == "" { assert.JSONEq(t, test.expectedBidExt, string(result), "Incorrect result") assert.NoError(t, err, "Error should not be returned") @@ -4157,6 +4413,7 @@ func TestStoredAuctionResponses(t *testing.T) { TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), } // Run test + outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) if test.errorExpected { assert.Error(t, err, "Error should be returned") @@ -4492,6 +4749,7 @@ func TestAuctionDebugEnabled(t *testing.T) { } debugLog := &DebugLog{DebugOverride: true, DebugEnabledOrOverridden: true} + resp, err := e.HoldAuction(ctx, auctionRequest, debugLog) assert.NoError(t, err, "error should be nil") @@ -4562,6 +4820,7 @@ func TestPassExperimentConfigsToHoldAuction(t *testing.T) { } debugLog := DebugLog{} + _, err = e.HoldAuction(context.Background(), auctionRequest, &debugLog) assert.NoError(t, err, "unexpected error occured") @@ -4624,7 +4883,7 @@ func TestCallSignHeader(t *testing.T) { func TestValidateBannerCreativeSize(t *testing.T) { exchange := exchange{bidValidationEnforcement: config.Validations{MaxCreativeWidth: 100, MaxCreativeHeight: 100}, - me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames(), nil, nil), + me: metricsConf.NewMetricsEngine(&config.Configuration{}, metricsConf.NewMetricsRegistry(), openrtb_ext.CoreBidderNames(), nil, nil), } testCases := []struct { description string @@ -4675,7 +4934,7 @@ func TestValidateBannerCreativeSize(t *testing.T) { func TestValidateBidAdM(t *testing.T) { exchange := exchange{bidValidationEnforcement: config.Validations{MaxCreativeWidth: 100, MaxCreativeHeight: 100}, - me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames(), nil, nil), + me: metricsConf.NewMetricsEngine(&config.Configuration{}, metricsConf.NewMetricsRegistry(), openrtb_ext.CoreBidderNames(), nil, nil), } testCases := []struct { description string diff --git a/exchange/floors_ow.go b/exchange/floors_ow.go new file mode 100644 index 00000000000..d4c12c71d27 --- /dev/null +++ b/exchange/floors_ow.go @@ -0,0 +1,23 @@ +package exchange + +import ( + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// floorsEnabled will return true if floors are enabled in both account and request level +func floorsEnabled(account config.Account, bidRequestWrapper *openrtb_ext.RequestWrapper) (bool, *openrtb_ext.PriceFloorRules) { + var ( + reqEnabled bool + floorRules *openrtb_ext.PriceFloorRules + ) + + if requestExt, err := bidRequestWrapper.GetRequestExt(); err == nil { + if prebidExt := requestExt.GetPrebid(); prebidExt != nil { + reqEnabled = prebidExt.Floors.GetEnabled() + floorRules = prebidExt.Floors + } + } + + return account.PriceFloors.Enabled && reqEnabled, floorRules +} diff --git a/exchange/floors_ow_test.go b/exchange/floors_ow_test.go new file mode 100644 index 00000000000..38cc289ee51 --- /dev/null +++ b/exchange/floors_ow_test.go @@ -0,0 +1,157 @@ +package exchange + +import ( + "encoding/json" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/boolutil" + "github.com/stretchr/testify/assert" +) + +func TestFloorsEnabled(t *testing.T) { + type args struct { + account config.Account + bidRequestWrapper *openrtb_ext.RequestWrapper + } + tests := []struct { + name string + args args + wantEnabled bool + wantRules *openrtb_ext.PriceFloorRules + }{ + { + name: "Floors data available in request and its enabled", + args: args{ + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + }, + }, + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: func() json.RawMessage { + ext := make(map[string]interface{}) + prebidExt := openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: boolutil.BoolPtr(true), + FloorMin: 2, + FloorMinCur: "INR", + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + }, + }, + } + ext["prebid"] = prebidExt + data, _ := json.Marshal(ext) + return data + }(), + }, + }, + }, + wantEnabled: true, + wantRules: func() *openrtb_ext.PriceFloorRules { + floors := openrtb_ext.PriceFloorRules{ + Enabled: boolutil.BoolPtr(true), + FloorMin: 2, + FloorMinCur: "INR", + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + }, + } + return &floors + }(), + }, + { + name: "Floors data available in request and floors is disabled", + args: args{ + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: false, + }, + }, + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: func() json.RawMessage { + ext := map[string]interface{}{ + "prebid": openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: boolutil.BoolPtr(true), + FloorMin: 2, + FloorMinCur: "INR", + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + }, + }, + }, + } + data, _ := json.Marshal(ext) + return data + }(), + }, + }, + }, + wantEnabled: false, + wantRules: func() *openrtb_ext.PriceFloorRules { + floors := openrtb_ext.PriceFloorRules{ + Enabled: boolutil.BoolPtr(true), + FloorMin: 2, + FloorMinCur: "INR", + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + }, + } + return &floors + }(), + }, + { + name: "Floors data is nil in request but floors is enabled in account", + args: args{ + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + }, + }, + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: func() json.RawMessage { + ext := map[string]interface{}{ + "prebid": openrtb_ext.ExtRequestPrebid{}, + } + data, _ := json.Marshal(ext) + return data + }(), + }, + }, + }, + wantEnabled: true, + wantRules: nil, + }, + { + name: "extension is empty but floors is enabled in account", + args: args{ + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + }, + }, + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + }, + wantEnabled: false, + wantRules: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotEnabled, gotRules := floorsEnabled(tt.args.account, tt.args.bidRequestWrapper) + if gotEnabled != tt.wantEnabled { + t.Errorf("floorsEnabled() got = %v, want %v", gotEnabled, tt.wantEnabled) + } + assert.Equal(t, tt.wantRules, gotRules, "Invalid Floors rules") + }) + } +} diff --git a/exchange/non_bid_reason.go b/exchange/non_bid_reason.go index 9a6f1f6bf61..8f58416d5f8 100644 --- a/exchange/non_bid_reason.go +++ b/exchange/non_bid_reason.go @@ -8,6 +8,10 @@ type NonBidReason int const ( NoBidUnknownError NonBidReason = 0 // No Bid - General ResponseRejectedCategoryMappingInvalid NonBidReason = 303 // Response Rejected - Category Mapping Invalid + + // vendor specific NonBidReasons (500+) + RequestBlockedSlotNotMapped NonBidReason = 503 + RequestBlockedPartnerThrottle NonBidReason = 504 ) // Ptr returns pointer to own value. diff --git a/exchange/price_granularity.go b/exchange/price_granularity.go index ee3104605d7..6771251cfe3 100644 --- a/exchange/price_granularity.go +++ b/exchange/price_granularity.go @@ -41,7 +41,8 @@ func GetPriceBucket(bid openrtb2.Bid, targetingData targetData) string { } } - if cpm > bucketMax { + //OTT-603: Adding Test Price Granularity + if config.Test || cpm > bucketMax { // We are over max, just return that cpmStr = strconv.FormatFloat(bucketMax, 'f', precision, 64) } else if increment > 0 { diff --git a/exchange/price_granularity_test.go b/exchange/price_granularity_test.go index 810dbcdf45a..2fa75d8e258 100644 --- a/exchange/price_granularity_test.go +++ b/exchange/price_granularity_test.go @@ -17,7 +17,7 @@ func TestGetPriceBucketString(t *testing.T) { high, _ := openrtb_ext.NewPriceGranularityFromLegacyID("high") auto, _ := openrtb_ext.NewPriceGranularityFromLegacyID("auto") dense, _ := openrtb_ext.NewPriceGranularityFromLegacyID("dense") - + testPG, _ := openrtb_ext.NewPriceGranularityFromLegacyID("testpg") custom1 := openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -72,6 +72,7 @@ func TestGetPriceBucketString(t *testing.T) { {"dense", targetData{priceGranularity: dense}, "1.87"}, {"custom1", targetData{priceGranularity: custom1}, "1.86"}, {"custom2", targetData{priceGranularity: custom2}, "1.50"}, + {"testpg", targetData{priceGranularity: testPG}, "50.00"}, }, }, { @@ -85,6 +86,7 @@ func TestGetPriceBucketString(t *testing.T) { {"dense", targetData{priceGranularity: dense}, "5.70"}, {"custom1", targetData{priceGranularity: custom1}, "5.70"}, {"custom2", targetData{priceGranularity: custom2}, "5.10"}, + {"testpg", targetData{priceGranularity: testPG}, "50.00"}, }, }, { @@ -189,6 +191,13 @@ func TestGetPriceBucketString(t *testing.T) { bid: openrtb2.Bid{Price: math.MaxFloat64}, testCases: []aTest{{"low", targetData{priceGranularity: low}, "5.00"}}, }, + { + groupDesc: "cpm above max test price granularity value", + bid: openrtb2.Bid{Price: 60}, + testCases: []aTest{ + {"testpg", targetData{priceGranularity: testPG}, "50.00"}, + }, + }, } for _, testGroup := range testGroups { diff --git a/exchange/seat_non_bids.go b/exchange/seat_non_bids.go index 78c1b23e3f3..bd37ceee0b7 100644 --- a/exchange/seat_non_bids.go +++ b/exchange/seat_non_bids.go @@ -33,6 +33,18 @@ func (snb *nonBids) addBid(bid *entities.PbsOrtbBid, nonBidReason int, seat stri MType: bid.Bid.MType, OriginalBidCPM: bid.OriginalBidCPM, OriginalBidCur: bid.OriginalBidCur, + + //OW specific + ID: bid.Bid.ID, + DealPriority: bid.DealPriority, + DealTierSatisfied: bid.DealTierSatisfied, + Meta: bid.BidMeta, + Targeting: bid.BidTargets, + Type: bid.BidType, + Video: bid.BidVideo, + BidId: bid.GeneratedBidID, + Floors: bid.BidFloors, + OriginalBidCPMUSD: bid.OriginalBidCPMUSD, }}, }, } diff --git a/exchange/targeting.go b/exchange/targeting.go index d278c2f5873..f5ff097d4fd 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -96,6 +96,7 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi if len(categoryMapping) > 0 { targData.addKeys(targets, openrtb_ext.HbCategoryDurationKey, categoryMapping[topBid.Bid.ID], targetingBidderCode, isOverallWinner, truncateTargetAttr) } + targData.addBidderKeys(targets, topBid.BidTargets) topBid.BidTargets = targets } } @@ -136,3 +137,11 @@ func getMultiBidMeta(multiBidMap map[string]openrtb_ext.ExtMultiBid, bidder stri return "", openrtb_ext.DefaultBidLimit } + +func (targData *targetData) addBidderKeys(keys map[string]string, bidderKeys map[string]string) { + if targData.includeBidderKeys { + for index, element := range bidderKeys { + keys[index] = element + } + } +} diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 8742a4f5d2a..a73f7c3fabb 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -130,6 +130,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op } debugLog := DebugLog{} + bidResp, err := ex.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { diff --git a/exchange/utils.go b/exchange/utils.go index dfe1fe448ba..d33f0c9fe35 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -60,6 +60,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, requestExt *openrtb_ext.ExtRequest, gdprDefaultValue gdpr.Signal, bidAdjustmentFactors map[string]float64, ) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { + req := auctionReq.BidRequestWrapper aliases, errs := parseAliases(req.BidRequest) if len(errs) > 0 { @@ -95,6 +96,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, errs = append(errs, err) } } + updateContentObjectForBidder(allBidderRequests, requestExt) if auctionReq.Account.PriceFloors.IsAdjustForBidAdjustmentEnabled() { //Apply BidAdjustmentFactor to imp.BidFloor @@ -445,6 +447,7 @@ func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, request prebid.MultiBid = buildRequestExtMultiBid(bidder, requestExtParsed.Prebid.MultiBid, alternateBidderCodes) prebid.Sdk = requestExtParsed.Prebid.Sdk prebid.Server = requestExtParsed.Prebid.Server + prebid.KeyVal = requestExtParsed.Prebid.KeyVal } // Marshal New Prebid Object @@ -657,6 +660,11 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map } } + // Dont send this to adapters + // if v, exists := impExtPrebid["floors"]; exists { + // sanitizedImpPrebidExt["floors"] = v + // } + // marshal sanitized imp[].ext.prebid if len(sanitizedImpPrebidExt) > 0 { if impExtPrebidJSON, err := jsonutil.Marshal(sanitizedImpPrebidExt); err == nil { diff --git a/exchange/utils_ow.go b/exchange/utils_ow.go new file mode 100644 index 00000000000..4c47dabdcd9 --- /dev/null +++ b/exchange/utils_ow.go @@ -0,0 +1,259 @@ +package exchange + +import ( + "encoding/json" + + "github.com/golang/glog" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func JLogf(msg string, obj interface{}) { + if glog.V(3) { + data, _ := json.Marshal(obj) + glog.Infof("[OPENWRAP] %v:%v", msg, string(data)) + } +} + +// updateContentObjectForBidder updates the content object for each bidder based on content transparency rules +func updateContentObjectForBidder(allBidderRequests []BidderRequest, requestExt *openrtb_ext.ExtRequest) { + if requestExt == nil || requestExt.Prebid.Transparency == nil || requestExt.Prebid.Transparency.Content == nil { + return + } + + rules := requestExt.Prebid.Transparency.Content + + if len(rules) == 0 { + return + } + + var contentObject *openrtb2.Content + isApp := false + bidderRequest := allBidderRequests[0] + if bidderRequest.BidRequest.App != nil && bidderRequest.BidRequest.App.Content != nil { + contentObject = bidderRequest.BidRequest.App.Content + isApp = true + } else if bidderRequest.BidRequest.Site != nil && bidderRequest.BidRequest.Site.Content != nil { + contentObject = bidderRequest.BidRequest.Site.Content + } else { + return + } + + // Dont send content object if no rule and default is not present + var defaultRule = openrtb_ext.TransparencyRule{} + if rule, ok := rules["default"]; ok { + defaultRule = rule + } + + for _, bidderRequest := range allBidderRequests { + var newContentObject *openrtb2.Content + + rule, ok := rules[string(bidderRequest.BidderName)] + if !ok { + rule = defaultRule + } + + if len(rule.Keys) != 0 { + newContentObject = createNewContentObject(contentObject, rule.Include, rule.Keys) + } else if rule.Include { + newContentObject = contentObject + } + deepCopyContentObj(bidderRequest.BidRequest, newContentObject, isApp) + } +} + +func deepCopyContentObj(request *openrtb2.BidRequest, contentObject *openrtb2.Content, isApp bool) { + if isApp { + app := *request.App + app.Content = contentObject + request.App = &app + } else { + site := *request.Site + site.Content = contentObject + request.Site = &site + } +} + +// func createNewContentObject(contentObject *openrtb2.Content, include bool, keys []string) *openrtb2.Content { +// if include { +// return includeKeys(contentObject, keys) +// } +// return excludeKeys(contentObject, keys) + +// } + +// func excludeKeys(contentObject *openrtb2.Content, keys []string) *openrtb2.Content { +// newContentObject := *contentObject + +// keyMap := make(map[string]struct{}, 1) +// for _, key := range keys { +// keyMap[key] = struct{}{} +// } + +// rt := reflect.TypeOf(newContentObject) +// for i := 0; i < rt.NumField(); i++ { +// key := strings.Split(rt.Field(i).Tag.Get("json"), ",")[0] // remove omitempty, etc +// if _, ok := keyMap[key]; ok { +// reflect.ValueOf(&newContentObject).Elem().FieldByName(rt.Field(i).Name).Set(reflect.Zero(rt.Field(i).Type)) +// } +// } + +// return &newContentObject +// } + +// func includeKeys(contentObject *openrtb2.Content, keys []string) *openrtb2.Content { +// newContentObject := openrtb2.Content{} +// v := reflect.ValueOf(contentObject).Elem() +// keyMap := make(map[string]struct{}, 1) +// for _, key := range keys { +// keyMap[key] = struct{}{} +// } + +// rt := reflect.TypeOf(newContentObject) +// rvElem := reflect.ValueOf(&newContentObject).Elem() +// for i := 0; i < rt.NumField(); i++ { +// field := rt.Field(i) +// key := strings.Split(field.Tag.Get("json"), ",")[0] // remove omitempty, etc +// if _, ok := keyMap[key]; ok { +// rvElem.FieldByName(field.Name).Set(v.FieldByName(field.Name)) +// } +// } + +// return &newContentObject +// } + +func createNewContentObject(contentObject *openrtb2.Content, include bool, keys []string) *openrtb2.Content { + newContentObject := &openrtb2.Content{} + if !include { + *newContentObject = *contentObject + for _, key := range keys { + + switch key { + case "id": + newContentObject.ID = "" + case "episode": + newContentObject.Episode = 0 + case "title": + newContentObject.Title = "" + case "series": + newContentObject.Series = "" + case "season": + newContentObject.Season = "" + case "artist": + newContentObject.Artist = "" + case "genre": + newContentObject.Genre = "" + case "album": + newContentObject.Album = "" + case "isrc": + newContentObject.ISRC = "" + case "producer": + newContentObject.Producer = nil + case "url": + newContentObject.URL = "" + case "cat": + newContentObject.Cat = nil + case "prodq": + newContentObject.ProdQ = nil + case "videoquality": + newContentObject.VideoQuality = nil + case "context": + newContentObject.Context = 0 + case "contentrating": + newContentObject.ContentRating = "" + case "userrating": + newContentObject.UserRating = "" + case "qagmediarating": + newContentObject.QAGMediaRating = 0 + case "keywords": + newContentObject.Keywords = "" + case "livestream": + newContentObject.LiveStream = 0 + case "sourcerelationship": + newContentObject.SourceRelationship = 0 + case "len": + newContentObject.Len = 0 + case "language": + newContentObject.Language = "" + case "embeddable": + newContentObject.Embeddable = 0 + case "data": + newContentObject.Data = nil + case "ext": + newContentObject.Ext = nil + } + + } + return newContentObject + } + + for _, key := range keys { + switch key { + case "id": + newContentObject.ID = contentObject.ID + case "episode": + newContentObject.Episode = contentObject.Episode + case "title": + newContentObject.Title = contentObject.Title + case "series": + newContentObject.Series = contentObject.Series + case "season": + newContentObject.Season = contentObject.Season + case "artist": + newContentObject.Artist = contentObject.Artist + case "genre": + newContentObject.Genre = contentObject.Genre + case "album": + newContentObject.Album = contentObject.Album + case "isrc": + newContentObject.ISRC = contentObject.ISRC + case "producer": + if contentObject.Producer != nil { + producer := *contentObject.Producer + newContentObject.Producer = &producer + } + case "url": + newContentObject.URL = contentObject.URL + case "cat": + newContentObject.Cat = contentObject.Cat + case "prodq": + if contentObject.ProdQ != nil { + prodQ := *contentObject.ProdQ + newContentObject.ProdQ = &prodQ + } + case "videoquality": + if contentObject.VideoQuality != nil { + videoQuality := *contentObject.VideoQuality + newContentObject.VideoQuality = &videoQuality + } + case "context": + newContentObject.Context = contentObject.Context + case "contentrating": + newContentObject.ContentRating = contentObject.ContentRating + case "userrating": + newContentObject.UserRating = contentObject.UserRating + case "qagmediarating": + newContentObject.QAGMediaRating = contentObject.QAGMediaRating + case "keywords": + newContentObject.Keywords = contentObject.Keywords + case "livestream": + newContentObject.LiveStream = contentObject.LiveStream + case "sourcerelationship": + newContentObject.SourceRelationship = contentObject.SourceRelationship + case "len": + newContentObject.Len = contentObject.Len + case "language": + newContentObject.Language = contentObject.Language + case "embeddable": + newContentObject.Embeddable = contentObject.Embeddable + case "data": + if contentObject.Data != nil { + newContentObject.Data = contentObject.Data + } + case "ext": + newContentObject.Ext = contentObject.Ext + } + } + + return newContentObject +} diff --git a/exchange/utils_ow_test.go b/exchange/utils_ow_test.go new file mode 100644 index 00000000000..81afc07ab4f --- /dev/null +++ b/exchange/utils_ow_test.go @@ -0,0 +1,1049 @@ +package exchange + +import ( + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func Test_updateContentObjectForBidder(t *testing.T) { + + createBidderRequest := func(BidRequest *openrtb2.BidRequest) []BidderRequest { + newReq := *BidRequest + newReq.ID = "2" + return []BidderRequest{{ + BidderName: "pubmatic", + BidRequest: BidRequest, + }, + { + BidderName: "appnexus", + BidRequest: &newReq, + }, + } + } + + type args struct { + BidRequest *openrtb2.BidRequest + requestExt *openrtb_ext.ExtRequest + } + tests := []struct { + name string + args args + wantedAllBidderRequests []BidderRequest + }{ + { + name: "No Transparency Object", + args: args{ + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + }, + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + }, + }, + }, + }, + }, + }, + { + name: "No Content Object in App/Site", + args: args{ + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + }, + Site: &openrtb2.Site{ + ID: "1", + Name: "Site1", + }, + }, + + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{ + "pubmatic": { + Include: true, + }, + }, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + }, + Site: &openrtb2.Site{ + ID: "1", + Name: "Site1", + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + }, + Site: &openrtb2.Site{ + ID: "1", + Name: "Site1", + }, + }, + }, + }, + }, + { + name: "No partner/ default rules in tranpsarency", + args: args{ + BidRequest: &openrtb2.BidRequest{ + ID: "1", + Site: &openrtb2.Site{ + ID: "1", + Name: "Test", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{}, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + Site: &openrtb2.Site{ + ID: "1", + Name: "Test", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + Site: &openrtb2.Site{ + ID: "1", + Name: "Test", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + }, + }, + }, + { + name: "Include All keys for bidder", + args: args{ + + BidRequest: &openrtb2.BidRequest{ + ID: "1", + Site: &openrtb2.Site{ + ID: "1", + Name: "Test", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{ + "pubmatic": { + Include: true, + Keys: []string{}, + }, + "appnexus": { + Include: false, + Keys: []string{}, + }, + }, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + Site: &openrtb2.Site{ + ID: "1", + Name: "Test", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + Site: &openrtb2.Site{ + ID: "1", + Name: "Test", + }, + }, + }, + }, + }, + { + name: "Exclude All keys for pubmatic bidder", + args: args{ + + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{ + "pubmatic": { + Include: false, + Keys: []string{}, + }, + "appnexus": { + Include: true, + Keys: []string{}, + }, + }, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + }, + }, + }, + { + name: "Include title field for pubmatic bidder", + args: args{ + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{ + "pubmatic": { + Include: true, + Keys: []string{"title"}, + }, + "appnexus": { + Include: false, + Keys: []string{"genre"}, + }, + }, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + }, + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + }, + }, + }, + }, + }, + }, + { + name: "Exclude title field for pubmatic bidder", + args: args{ + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{ + "pubmatic": { + Include: false, + Keys: []string{"title"}, + }, + "appnexus": { + Include: true, + Keys: []string{"genre"}, + }, + }, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Genre: "Genre1", + }, + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Genre: "Genre1", + }, + }, + }, + }, + }, + }, + { + name: "Use default rule for pubmatic bidder", + args: args{ + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + Series: "Series1", + Season: "Season1", + Artist: "Artist1", + Album: "Album1", + ISRC: "isrc1", + Producer: &openrtb2.Producer{}, + }, + }, + }, + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{ + "default": { + Include: true, + Keys: []string{ + "id", "episode", "series", "season", "artist", "genre", "album", "isrc", "producer", "url", "cat", "prodq", "videoquality", "context", "contentrating", "userrating", "qagmediarating", "livestream", "sourcerelationship", "len", "language", "embeddable", "data", "ext"}, + }, + "pubmatic": { + Include: true, + Keys: []string{"title", "genre"}, + }, + }, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Genre: "Genre1", + Series: "Series1", + Season: "Season1", + Artist: "Artist1", + Album: "Album1", + ISRC: "isrc1", + Producer: &openrtb2.Producer{}, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + allBidderRequests := createBidderRequest(tt.args.BidRequest) + updateContentObjectForBidder(allBidderRequests, tt.args.requestExt) + assert.Equal(t, tt.wantedAllBidderRequests, allBidderRequests, tt.name) + }) + } +} + +func Benchmark_updateContentObjectForBidder(b *testing.B) { + + createBidderRequest := func(BidRequest *openrtb2.BidRequest) []BidderRequest { + newReq := *BidRequest + newReq.ID = "2" + return []BidderRequest{{ + BidderName: "pubmatic", + BidRequest: BidRequest, + }, + { + BidderName: "appnexus", + BidRequest: &newReq, + }, + } + } + + type args struct { + BidRequest *openrtb2.BidRequest + requestExt *openrtb_ext.ExtRequest + } + tests := []struct { + name string + args args + wantedAllBidderRequests []BidderRequest + }{ + { + name: "No Transparency Object", + args: args{ + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + }, + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + }, + }, + }, + }, + }, + }, + { + name: "No Content Object in App/Site", + args: args{ + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + }, + Site: &openrtb2.Site{ + ID: "1", + Name: "Site1", + }, + }, + + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{ + "pubmatic": { + Include: true, + }, + }, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + }, + Site: &openrtb2.Site{ + ID: "1", + Name: "Site1", + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + }, + Site: &openrtb2.Site{ + ID: "1", + Name: "Site1", + }, + }, + }, + }, + }, + { + name: "No partner/ default rules in tranpsarency", + args: args{ + BidRequest: &openrtb2.BidRequest{ + ID: "1", + Site: &openrtb2.Site{ + ID: "1", + Name: "Test", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{}, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + Site: &openrtb2.Site{ + ID: "1", + Name: "Test", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + Site: &openrtb2.Site{ + ID: "1", + Name: "Test", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + }, + }, + }, + { + name: "Include All keys for bidder", + args: args{ + + BidRequest: &openrtb2.BidRequest{ + ID: "1", + Site: &openrtb2.Site{ + ID: "1", + Name: "Test", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{ + "pubmatic": { + Include: true, + Keys: []string{}, + }, + "appnexus": { + Include: false, + Keys: []string{}, + }, + }, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + Site: &openrtb2.Site{ + ID: "1", + Name: "Test", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + Site: &openrtb2.Site{ + ID: "1", + Name: "Test", + }, + }, + }, + }, + }, + { + name: "Exclude All keys for pubmatic bidder", + args: args{ + + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{ + "pubmatic": { + Include: false, + Keys: []string{}, + }, + "appnexus": { + Include: true, + Keys: []string{}, + }, + }, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + }, + }, + }, + { + name: "Include title field for pubmatic bidder", + args: args{ + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{ + "pubmatic": { + Include: true, + Keys: []string{"title"}, + }, + "appnexus": { + Include: false, + Keys: []string{"genre"}, + }, + }, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + }, + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + }, + }, + }, + }, + }, + }, + { + name: "Exclude title field for pubmatic bidder", + args: args{ + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{ + "pubmatic": { + Include: false, + Keys: []string{"title"}, + }, + "appnexus": { + Include: true, + Keys: []string{"genre"}, + }, + }, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Genre: "Genre1", + }, + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Genre: "Genre1", + }, + }, + }, + }, + }, + }, + { + name: "Use default rule for pubmatic bidder", + args: args{ + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + Series: "Series1", + Season: "Season1", + Artist: "Artist1", + Album: "Album1", + ISRC: "isrc1", + Producer: &openrtb2.Producer{}, + }, + }, + }, + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Transparency: &openrtb_ext.TransparencyExt{ + Content: map[string]openrtb_ext.TransparencyRule{ + "default": { + Include: true, + Keys: []string{ + "id", "episode", "series", "season", "artist", "genre", "album", "isrc", "producer", "url", "cat", "prodq", "videoquality", "context", "contentrating", "userrating", "qagmediarating", "livestream", "sourcerelationship", "len", "language", "embeddable", "data", "ext"}, + }, + "pubmatic": { + Include: true, + Keys: []string{"title", "genre"}, + }, + }, + }, + }, + }, + }, + wantedAllBidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "1", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Title: "Title1", + Genre: "Genre1", + }, + }, + }, + }, + { + BidderName: "appnexus", + BidRequest: &openrtb2.BidRequest{ + ID: "2", + App: &openrtb2.App{ + ID: "1", + Name: "Test", + Bundle: "com.pubmatic.app", + Content: &openrtb2.Content{ + Genre: "Genre1", + Series: "Series1", + Season: "Season1", + Artist: "Artist1", + Album: "Album1", + ISRC: "isrc1", + Producer: &openrtb2.Producer{}, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + allBidderRequests := createBidderRequest(tt.args.BidRequest) + for i := 0; i < b.N; i++ { + updateContentObjectForBidder(allBidderRequests, tt.args.requestExt) + } + //assert.Equal(t, tt.wantedAllBidderRequests, allBidderRequests, tt.name) + }) + } +} diff --git a/floors/enforce.go b/floors/enforce.go index e1f4d8d015b..e5adef58ce6 100644 --- a/floors/enforce.go +++ b/floors/enforce.go @@ -92,7 +92,7 @@ func updateBidExt(bidRequestWrapper *openrtb_ext.RequestWrapper, seatBids map[op for _, bid := range seatBid.Bids { reqImp, ok := impMap[bid.Bid.ImpID] if ok { - updateBidExtWithFloors(reqImp, bid, reqImp.BidFloorCur) + updateBidExtWithFloors(reqImp, bid) } } } @@ -138,6 +138,11 @@ func enforceFloorToBids(bidRequestWrapper *openrtb_ext.RequestWrapper, seatBids bidPrice := rate * bid.Bid.Price if (bidPrice + floorPrecision) < reqImp.BidFloor { + if bid.BidFloors != nil { + // Need USD for OW analytics + // TODO: Move this to better place where 'conversions' (deduced from host+request) is available + // bid.BidFloors.FloorValueUSD = getOriginalBidCpmUsd(reqImp.BidFloor, reqImp.BidFloorCur, conversions) + } rejectedBid := &entities.PbsOrtbSeatBid{ Currency: seatBid.Currency, Seat: seatBid.Seat, @@ -236,25 +241,27 @@ func getCurrencyConversionRate(seatBidCur, reqImpCur string, conversions currenc } // updateBidExtWithFloors updates floors related details in bid extension -func updateBidExtWithFloors(reqImp *openrtb_ext.ImpWrapper, bid *entities.PbsOrtbBid, floorCurrency string) { +func updateBidExtWithFloors(reqImp *openrtb_ext.ImpWrapper, bid *entities.PbsOrtbBid) { impExt, err := reqImp.GetImpExt() if err != nil { return } - var bidExtFloors openrtb_ext.ExtBidPrebidFloors prebidExt := impExt.GetPrebid() - if prebidExt == nil || prebidExt.Floors == nil { - if reqImp.BidFloor > 0 { - bidExtFloors.FloorValue = reqImp.BidFloor - bidExtFloors.FloorCurrency = reqImp.BidFloorCur - bid.BidFloors = &bidExtFloors + if prebidExt != nil && prebidExt.Floors != nil { + bid.BidFloors = &openrtb_ext.ExtBidPrebidFloors{ + FloorRule: prebidExt.Floors.FloorRule, + FloorRuleValue: prebidExt.Floors.FloorRuleValue, + FloorValue: prebidExt.Floors.FloorValue, + FloorCurrency: reqImp.BidFloorCur, + } + return + } + + if reqImp.Imp != nil && reqImp.Imp.BidFloor != 0 { + bid.BidFloors = &openrtb_ext.ExtBidPrebidFloors{ + FloorValue: reqImp.Imp.BidFloor, + FloorCurrency: reqImp.BidFloorCur, } - } else { - bidExtFloors.FloorRule = prebidExt.Floors.FloorRule - bidExtFloors.FloorRuleValue = prebidExt.Floors.FloorRuleValue - bidExtFloors.FloorValue = prebidExt.Floors.FloorValue - bidExtFloors.FloorCurrency = floorCurrency - bid.BidFloors = &bidExtFloors } } diff --git a/floors/enforce_ow.go b/floors/enforce_ow.go new file mode 100644 index 00000000000..9568ca44b1e --- /dev/null +++ b/floors/enforce_ow.go @@ -0,0 +1,8 @@ +package floors + +import "github.com/prebid/prebid-server/v2/currency" + +func getOriginalBidCpmUsd(price float64, from string, conversions currency.Conversions) float64 { + rate, _ := getCurrencyConversionRate(from, "USD", conversions) + return rate * price +} diff --git a/floors/enforce_test.go b/floors/enforce_test.go index 085506c3411..e2cd3dcc038 100644 --- a/floors/enforce_test.go +++ b/floors/enforce_test.go @@ -3,7 +3,6 @@ package floors import ( "encoding/json" "errors" - "reflect" "testing" "github.com/prebid/openrtb/v19/openrtb2" @@ -496,6 +495,7 @@ func TestEnforce(t *testing.T) { Currency: "USD", }, }, + expErrs: nil, expRejectedBids: []*entities.PbsOrtbSeatBid{}, }, { @@ -534,6 +534,7 @@ func TestEnforce(t *testing.T) { Currency: "USD", }, }, + expErrs: nil, expRejectedBids: []*entities.PbsOrtbSeatBid{}, }, { @@ -566,6 +567,7 @@ func TestEnforce(t *testing.T) { expEligibleBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ "pubmatic": { Bids: []*entities.PbsOrtbBid{ + // {Bid: &openrtb2.Bid{ID: "some-bid-1", Price: 1.2, ImpID: "some-impression-id-1"}, BidFloors: &openrtb_ext.ExtBidPrebidFloors{FloorValue: 5.01, FloorValueUSD: 0, FloorCurrency: "USD"}}, {Bid: &openrtb2.Bid{ID: "some-bid-1", Price: 1.2, ImpID: "some-impression-id-1"}, BidFloors: &openrtb_ext.ExtBidPrebidFloors{FloorValue: 5.01, FloorCurrency: "USD"}}, }, Seat: "pubmatic", @@ -656,12 +658,14 @@ func TestEnforce(t *testing.T) { { Seat: "pubmatic", Currency: "USD", - Bids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{ID: "some-bid-1", Price: 1.2, DealID: "deal_Id_1", ImpID: "some-impression-id-1"}, BidFloors: &openrtb_ext.ExtBidPrebidFloors{FloorCurrency: "USD", FloorValue: 20.01}}}, + // Bids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{ID: "some-bid-1", Price: 1.2, DealID: "deal_Id_1", ImpID: "some-impression-id-1"}, BidFloors: &openrtb_ext.ExtBidPrebidFloors{FloorCurrency: "USD", FloorValue: 20.01, FloorValueUSD: 20.01}}}, + Bids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{ID: "some-bid-1", Price: 1.2, DealID: "deal_Id_1", ImpID: "some-impression-id-1"}, BidFloors: &openrtb_ext.ExtBidPrebidFloors{FloorCurrency: "USD", FloorValue: 20.01}}}, }, { Seat: "appnexus", Currency: "USD", - Bids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{ID: "some-bid-11", Price: 0.5, DealID: "deal_Id_3", ImpID: "some-impression-id-1"}, BidFloors: &openrtb_ext.ExtBidPrebidFloors{FloorCurrency: "USD", FloorValue: 20.01}}}, + // Bids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{ID: "some-bid-11", Price: 0.5, DealID: "deal_Id_3", ImpID: "some-impression-id-1"}, BidFloors: &openrtb_ext.ExtBidPrebidFloors{FloorCurrency: "USD", FloorValue: 20.01, FloorValueUSD: 20.01}}}, + Bids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{ID: "some-bid-11", Price: 0.5, DealID: "deal_Id_3", ImpID: "some-impression-id-1"}, BidFloors: &openrtb_ext.ExtBidPrebidFloors{FloorCurrency: "USD", FloorValue: 20.01}}}, }, }, expErrs: []error{}, @@ -715,7 +719,8 @@ func TestEnforce(t *testing.T) { { Seat: "appnexus", Currency: "USD", - Bids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{ID: "some-bid-11", Price: 4.5, ImpID: "some-impression-id-1"}, BidFloors: &openrtb_ext.ExtBidPrebidFloors{FloorValue: 5.01, FloorCurrency: "USD"}}}, + // Bids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{ID: "some-bid-11", Price: 4.5, ImpID: "some-impression-id-1"}, BidFloors: &openrtb_ext.ExtBidPrebidFloors{FloorValue: 5.01, FloorValueUSD: 5.01, FloorCurrency: "USD"}}}, + Bids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{ID: "some-bid-11", Price: 4.5, ImpID: "some-impression-id-1"}, BidFloors: &openrtb_ext.ExtBidPrebidFloors{FloorValue: 5.01, FloorCurrency: "USD"}}}, }, }, expErrs: []error{}, @@ -726,7 +731,7 @@ func TestEnforce(t *testing.T) { assert.Equal(t, tt.expErrs, actErrs, tt.name) assert.ElementsMatch(t, tt.expRejectedBids, actRejecteBids, tt.name) - if !reflect.DeepEqual(tt.expEligibleBids, actEligibleBids) { + if !assert.Equal(t, tt.expEligibleBids, actEligibleBids) { assert.Failf(t, "eligible bids don't match", "Expected: %v, Got: %v", tt.expEligibleBids, actEligibleBids) } } @@ -734,9 +739,8 @@ func TestEnforce(t *testing.T) { func TestUpdateBidExtWithFloors(t *testing.T) { type args struct { - reqImp *openrtb_ext.ImpWrapper - bid *entities.PbsOrtbBid - floorCurrency string + reqImp *openrtb_ext.ImpWrapper + bid *entities.PbsOrtbBid } tests := []struct { name string @@ -755,7 +759,6 @@ func TestUpdateBidExtWithFloors(t *testing.T) { AdM: "Adm", }, }, - floorCurrency: "USD", }, expBidFloor: &openrtb_ext.ExtBidPrebidFloors{ FloorValue: 10, @@ -772,18 +775,16 @@ func TestUpdateBidExtWithFloors(t *testing.T) { AdM: "Adm", }, }, - floorCurrency: "USD", }, expBidFloor: &openrtb_ext.ExtBidPrebidFloors{ FloorRule: "test|123|xyz", FloorRuleValue: 5.5, FloorValue: 5.5, - FloorCurrency: "USD", }, }, } for _, tt := range tests { - updateBidExtWithFloors(tt.args.reqImp, tt.args.bid, tt.args.floorCurrency) + updateBidExtWithFloors(tt.args.reqImp, tt.args.bid) assert.Equal(t, tt.expBidFloor, tt.args.bid.BidFloors, tt.name) } } @@ -1222,3 +1223,92 @@ func TestUpdateEnforcePBS(t *testing.T) { }) } } + +func TestUpdateBidExt(t *testing.T) { + type args struct { + bidRequestWrapper *openrtb_ext.RequestWrapper + seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid + } + tests := []struct { + name string + args args + want *openrtb_ext.ExtBidPrebidFloors + }{ + { + name: "Update Bid Ext with different impression id in request and seatbid", + args: args{ + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id-1", + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + BidFloor: 20.01, + BidFloorCur: "USD", + Ext: json.RawMessage(`{"prebid":{"floors":{"floorRule":"*|*|*","floorRuleValue":26.02,"floorValue":12,"floorMin":5,"FloorMinCur":"INR"}}}`), + }}, + Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":1,"data":{"currency":"USD","skiprate":100,"modelgroups":[{"modelversion":"version1","skiprate":10,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"},"values":{"*|*|*":20.01,"*|*|www.website1.com":16.01},"default":21}]},"enforcement":{"enforcepbs":true,"floordeals":true},"enabled":true,"skipped":false}}}`), + }, + }, + seatBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "some-bid-1", + Price: 1.2, + ImpID: "some-impression-id-2", + DealID: "1", + }, + }, + }, + Currency: "USD", + }, + }, + }, + }, + { + name: "Update Bid Ext with same impression id in request and seatbid", + args: args{ + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id-1", + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + BidFloor: 20.01, + BidFloorCur: "USD", + Ext: json.RawMessage(`{"prebid":{"floors":{"floorRule":"*|*|*","floorRuleValue":26.02,"floorValue":12,"floorMin":5,"FloorMinCur":"INR"}}}`), + }}, + Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":1,"data":{"currency":"USD","skiprate":100,"modelgroups":[{"modelversion":"version1","skiprate":10,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"},"values":{"*|*|*":20.01,"*|*|www.website1.com":16.01},"default":21}]},"enforcement":{"enforcepbs":true,"floordeals":true},"enabled":true,"skipped":false}}}`), + }, + }, + seatBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "some-bid-1", + Price: 1.2, + ImpID: "some-impression-id-1", + DealID: "1", + }, + }, + }, + Currency: "USD", + }, + }, + }, + want: &openrtb_ext.ExtBidPrebidFloors{ + FloorValue: 12, + FloorRuleValue: 26.02, + FloorRule: "*|*|*", + FloorCurrency: "USD", + }, + }, + } + for _, tt := range tests { + updateBidExt(tt.args.bidRequestWrapper, tt.args.seatBids) + assert.Equal(t, tt.want, tt.args.seatBids["pubmatic"].Bids[0].BidFloors, "Bid is not updated with data") + } +} diff --git a/floors/fetcher.go b/floors/fetcher.go index 6df43445010..a2865c593f3 100644 --- a/floors/fetcher.go +++ b/floors/fetcher.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" "math" "net/http" @@ -153,7 +154,7 @@ func (f *PriceFloorFetcher) Fetch(config config.AccountPriceFloors) (*openrtb_ex } func (f *PriceFloorFetcher) worker(fetchConfig fetchInfo) { - floorData, fetchedMaxAge := f.fetchAndValidate(fetchConfig.AccountFloorFetch) + floorData, fetchedMaxAge := f.fetchAndValidate(fetchConfig.AccountFloorFetch, f.metricEngine) if floorData != nil { // Reset retry count when data is successfully fetched fetchConfig.retryCount = 0 @@ -230,9 +231,10 @@ func (f *PriceFloorFetcher) Fetcher() { } } -func (f *PriceFloorFetcher) fetchAndValidate(config config.AccountFloorFetch) (*openrtb_ext.PriceFloorRules, int) { +func (f *PriceFloorFetcher) fetchAndValidate(config config.AccountFloorFetch, metricEngine metrics.MetricsEngine) (*openrtb_ext.PriceFloorRules, int) { floorResp, maxAge, err := f.fetchFloorRulesFromURL(config) if floorResp == nil || err != nil { + metricEngine.RecordDynamicFetchFailure(config.AccountID, "1") glog.Errorf("Error while fetching floor data from URL: %s, reason : %s", config.URL, err.Error()) return nil, 0 } @@ -244,11 +246,13 @@ func (f *PriceFloorFetcher) fetchAndValidate(config config.AccountFloorFetch) (* var priceFloors openrtb_ext.PriceFloorRules if err = json.Unmarshal(floorResp, &priceFloors.Data); err != nil { + metricEngine.RecordDynamicFetchFailure(config.AccountID, "2") glog.Errorf("Recieved invalid price floor json from URL: %s", config.URL) return nil, 0 } if err := validateRules(config, &priceFloors); err != nil { + metricEngine.RecordDynamicFetchFailure(config.AccountID, "3") glog.Errorf("Validation failed for floor JSON from URL: %s, reason: %s", config.URL, err.Error()) return nil, 0 } @@ -309,6 +313,10 @@ func validateRules(config config.AccountFloorFetch, priceFloors *openrtb_ext.Pri return errors.New("skip rate should be greater than or equal to 0 and less than 100") } + if priceFloors.Data.UseFetchDataRate != nil && (*priceFloors.Data.UseFetchDataRate < dataRateMin || *priceFloors.Data.UseFetchDataRate > dataRateMax) { + return fmt.Errorf("useFetchDataRate should be greater than or equal to %d and less than or equal to %d", dataRateMin, dataRateMax) + } + for _, modelGroup := range priceFloors.Data.ModelGroups { if len(modelGroup.Values) == 0 || len(modelGroup.Values) > config.MaxRules { return errors.New("invalid number of floor rules, floor rules should be greater than zero and less than MaxRules specified in account config") diff --git a/floors/fetcher_test.go b/floors/fetcher_test.go index 085fd3edd1b..baf447c29ae 100644 --- a/floors/fetcher_test.go +++ b/floors/fetcher_test.go @@ -16,6 +16,7 @@ import ( "github.com/prebid/prebid-server/v2/metrics" metricsConf "github.com/prebid/prebid-server/v2/metrics/config" "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/prebid/prebid-server/v2/util/timeutil" "github.com/stretchr/testify/assert" ) @@ -396,6 +397,32 @@ func TestValidatePriceFloorRules(t *testing.T) { }, wantErr: true, }, + { + name: "Invalid useFetchDataRate", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + SkipRate: 10, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + }}, + UseFetchDataRate: ptrutil.ToPtr(-11), + }, + }, + }, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -730,8 +757,9 @@ func TestFetchAndValidate(t *testing.T) { ppf := PriceFloorFetcher{ httpClient: mockHttpServer.Client(), } + tt.args.configs.URL = mockHttpServer.URL - got, got1 := ppf.fetchAndValidate(tt.args.configs) + got, got1 := ppf.fetchAndValidate(tt.args.configs, &metricsConf.NilMetricsEngine{}) if !reflect.DeepEqual(got, tt.want) { t.Errorf("fetchAndValidate() got = %v, want %v", got, tt.want) } diff --git a/floors/floors.go b/floors/floors.go index 3fdeb4c55ae..f5123b2ab4e 100644 --- a/floors/floors.go +++ b/floors/floors.go @@ -27,12 +27,15 @@ const ( modelWeightMin int = 1 enforceRateMin int = 0 enforceRateMax int = 100 + dataRateMin int = 0 + dataRateMax int = 100 floorPrecision float64 = 0.01 ) // EnrichWithPriceFloors checks for floors enabled in account and request and selects floors data from dynamic fetched if present // else selects floors data from req.ext.prebid.floors and update request with selected floors details func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, account config.Account, conversions currency.Conversions, priceFloorFetcher FloorFetcher) []error { + if bidRequestWrapper == nil || bidRequestWrapper.BidRequest == nil { return []error{errors.New("Empty bidrequest")} } @@ -96,11 +99,9 @@ func updateBidRequestWithFloors(extFloorRules *openrtb_ext.PriceFloorRules, requ imp.BidFloor = bidFloor imp.BidFloorCur = floorCur - if isRuleMatched { - err = updateImpExtWithFloorDetails(imp, matchedRule, floorVal, imp.BidFloor) - if err != nil { - floorErrList = append(floorErrList, err) - } + err = updateImpExtWithFloorDetails(imp, matchedRule, floorVal, imp.BidFloor) + if err != nil { + floorErrList = append(floorErrList, err) } } else { floorErrList = append(floorErrList, err) @@ -136,6 +137,15 @@ func isPriceFloorsEnabledForRequest(bidRequestWrapper *openrtb_ext.RequestWrappe return true } +// shouldUseFetchedData will check if to use fetched data or request data +func shouldUseFetchedData(rate *int) bool { + if rate == nil { + return true + } + randomNumber := rand.Intn(dataRateMax) + return randomNumber < *rate +} + // resolveFloors does selection of floors fields from request data and dynamic fetched data if dynamic fetch is enabled func resolveFloors(account config.Account, bidRequestWrapper *openrtb_ext.RequestWrapper, conversions currency.Conversions, priceFloorFetcher FloorFetcher) (*openrtb_ext.PriceFloorRules, []error) { var errList []error @@ -148,18 +158,19 @@ func resolveFloors(account config.Account, bidRequestWrapper *openrtb_ext.Reques account.PriceFloors.Fetcher.AccountID = account.ID var fetchResult *openrtb_ext.PriceFloorRules - var fetchStatus string + // var fetchStatus string + fetchStatus := openrtb_ext.FetchNone + if priceFloorFetcher != nil && account.PriceFloors.UseDynamicData { fetchResult, fetchStatus = priceFloorFetcher.Fetch(account.PriceFloors) } - - if fetchResult != nil && fetchStatus == openrtb_ext.FetchSuccess { + if fetchResult != nil && fetchStatus == openrtb_ext.FetchSuccess && shouldUseFetchedData(fetchResult.Data.UseFetchDataRate) { mergedFloor := mergeFloors(reqFloor, fetchResult, conversions) floorRules, errList = createFloorsFrom(mergedFloor, account, fetchStatus, openrtb_ext.FetchLocation) } else if reqFloor != nil { - floorRules, errList = createFloorsFrom(reqFloor, account, openrtb_ext.FetchNone, openrtb_ext.RequestLocation) + floorRules, errList = createFloorsFrom(reqFloor, account, fetchStatus, openrtb_ext.RequestLocation) } else { - floorRules, errList = createFloorsFrom(nil, account, openrtb_ext.FetchNone, openrtb_ext.NoDataLocation) + floorRules, errList = createFloorsFrom(nil, account, fetchStatus, openrtb_ext.NoDataLocation) } return floorRules, errList } @@ -197,9 +208,35 @@ func createFloorsFrom(floors *openrtb_ext.PriceFloorRules, account config.Accoun } } + if floorLocation == openrtb_ext.RequestLocation && finalFloors.Data == nil { + finalFloors.PriceFloorLocation = openrtb_ext.NoDataLocation + } + return finalFloors, floorModelErrList } +// resolveEnforcement does retrieval of enforceRate from request +func resolveEnforcement(enforcement *openrtb_ext.PriceFloorEnforcement, enforceRate int) *openrtb_ext.PriceFloorEnforcement { + if enforcement == nil { + enforcement = new(openrtb_ext.PriceFloorEnforcement) + } + enforcement.EnforceRate = enforceRate + return enforcement +} + +// getFloorsEnabledFlag gets floors enabled flag from request +func getFloorsEnabledFlag(reqFloors *openrtb_ext.PriceFloorRules) bool { + if reqFloors.Enabled != nil { + return *reqFloors.Enabled + } + return true +} + +// shouldUseDynamicFetchedFloor gets UseDynamicData flag from account level config +func shouldUseDynamicFetchedFloor(Account config.Account) bool { + return Account.PriceFloors.UseDynamicData +} + // extractFloorsFromRequest gets floors data from req.ext.prebid.floors func extractFloorsFromRequest(bidRequestWrapper *openrtb_ext.RequestWrapper) *openrtb_ext.PriceFloorRules { requestExt, err := bidRequestWrapper.GetRequestExt() diff --git a/floors/floors_ow.go b/floors/floors_ow.go new file mode 100644 index 00000000000..8ac9aa130a5 --- /dev/null +++ b/floors/floors_ow.go @@ -0,0 +1,14 @@ +package floors + +import ( + "github.com/prebid/openrtb/v19/openrtb2" +) + +func RequestHasFloors(bidRequest *openrtb2.BidRequest) bool { + for i := range bidRequest.Imp { + if bidRequest.Imp[i].BidFloor > 0 { + return true + } + } + return false +} diff --git a/floors/floors_ow_test.go b/floors/floors_ow_test.go new file mode 100644 index 00000000000..b828da6c668 --- /dev/null +++ b/floors/floors_ow_test.go @@ -0,0 +1,43 @@ +package floors + +import ( + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" +) + +func TestRequestHasFloors(t *testing.T) { + + tests := []struct { + name string + bidRequest *openrtb2.BidRequest + want bool + }{ + { + bidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + }, + want: false, + }, + { + bidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", BidFloor: 10, BidFloorCur: "USD", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + }, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := RequestHasFloors(tt.bidRequest); got != tt.want { + t.Errorf("RequestHasFloors() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/floors/floors_test.go b/floors/floors_test.go index 047b6738cd3..2ebfd887d98 100644 --- a/floors/floors_test.go +++ b/floors/floors_test.go @@ -11,6 +11,7 @@ import ( "github.com/prebid/prebid-server/v2/currency" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -456,7 +457,7 @@ func TestResolveFloors(t *testing.T) { }, } - testCases := []struct { + tt := []struct { name string bidRequestWrapper *openrtb_ext.RequestWrapper account config.Account @@ -629,7 +630,260 @@ func TestResolveFloors(t *testing.T) { FloorDeals: getTrue(), }, FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.NoDataLocation, + }, + }, + { + name: "Dynamic fetch enabled, floors from fetched selected and new URL is updated", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"floorendpoint":{"url":"http://test.com/floor"},"enabled":true}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + FloorDeals: getTrue(), + EnforceRate: 100, + }, + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "http://test.com/floor", + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 101", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates), &MockFetch{}) + if !reflect.DeepEqual(resolvedFloors, tc.expFloors) { + t.Errorf("resolveFloors error: \nreturn:\t%v\nwant:\t%v", printFloors(resolvedFloors), printFloors(tc.expFloors)) + } + }) + } +} + +type MockFetchDataRate0 struct { + FakeFetch func(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) +} + +func (m *MockFetchDataRate0) Stop() {} + +// type MockFetchDataRate0 struct{} + +func (m *MockFetchDataRate0) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + + if !configs.UseDynamicData { + return nil, openrtb_ext.FetchNone + } + priceFloors := openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model from fetched", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + }, + }, + }, + UseFetchDataRate: ptrutil.ToPtr(0), + }, + } + return &priceFloors, openrtb_ext.FetchSuccess +} + +type MockFetchDataRate100 struct { + FakeFetch func(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) +} + +func (m *MockFetchDataRate100) Stop() {} + +// type MockFetchDataRate100 struct{} + +func (m *MockFetchDataRate100) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + + if !configs.UseDynamicData { + return nil, openrtb_ext.FetchNone + } + priceFloors := openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model from fetched", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + }, + }, + }, + UseFetchDataRate: ptrutil.ToPtr(100), + }, + } + return &priceFloors, openrtb_ext.FetchSuccess +} + +func TestResolveFloorsWithUseDataRate(t *testing.T) { + rates := map[string]map[string]float64{ + "USD": { + "INR": 70, + "EUR": 0.9, + "JPY": 5.09, + }, + } + + testCases := []struct { + name string + bidRequestWrapper *openrtb_ext.RequestWrapper + account config.Account + conversions currency.Conversions + expErr []error + expFloors *openrtb_ext.PriceFloorRules + fetcher FloorFetcher + }{ + { + name: "Dynamic fetch enabled, floors from request selected as data rate 0", + fetcher: &MockFetchDataRate0{}, + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchSuccess, PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + FloorDeals: getTrue(), + EnforceRate: 100, + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model 1 from req", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 5, + "*|*|*": 7, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Dynamic fetch enabled, floors from fetched selected as data rate is 100", + fetcher: &MockFetchDataRate100{}, + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + FloorDeals: getTrue(), + EnforceRate: 100, + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model from fetched", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + }, + }, + }, + UseFetchDataRate: ptrutil.ToPtr(100), + }, }, }, { @@ -933,7 +1187,7 @@ func TestCreateFloorsFrom(t *testing.T) { }, want: &openrtb_ext.PriceFloorRules{ FetchStatus: openrtb_ext.FetchNone, - PriceFloorLocation: openrtb_ext.RequestLocation, + PriceFloorLocation: openrtb_ext.NoDataLocation, Enforcement: &openrtb_ext.PriceFloorEnforcement{ EnforcePBS: getTrue(), EnforceRate: 100, diff --git a/floors/rule.go b/floors/rule.go index 50c12462d28..04607746d7f 100644 --- a/floors/rule.go +++ b/floors/rule.go @@ -120,9 +120,13 @@ func updateImpExtWithFloorDetails(imp *openrtb_ext.ImpWrapper, matchedRule strin if extImpPrebid == nil { extImpPrebid = &openrtb_ext.ExtImpPrebid{} } + floorRuleValue := 0.0 + if matchedRule != "" { + floorRuleValue = floorRuleVal + } extImpPrebid.Floors = &openrtb_ext.ExtImpPrebidFloors{ FloorRule: matchedRule, - FloorRuleValue: floorRuleVal, + FloorRuleValue: roundToFourDecimals(floorRuleValue), FloorValue: floorVal, } impExt.SetPrebid(extImpPrebid) diff --git a/floors/rule_test.go b/floors/rule_test.go index 1e75956243d..8a981aa3436 100644 --- a/floors/rule_test.go +++ b/floors/rule_test.go @@ -214,6 +214,14 @@ func TestUpdateImpExtWithFloorDetails(t *testing.T) { imp: &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: 300, H: 250}, Ext: []byte(`{"prebid": {"test": true}}`)}}, expected: []byte(`{"prebid":{"floors":{"floorrule":"banner|www.test.com|*","floorrulevalue":5.5,"floorvalue":15.5}}}`), }, + { + name: "non matching rule", + matchedRule: "", + floorRuleVal: 5.5, + floorVal: 15.5, + imp: &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: 300, H: 250}, Ext: []byte(`{"prebid": {"test": true}}`)}}, + expected: []byte(`{"prebid":{"floors":{"floorvalue":15.5}}}`), + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index 64424f5ee69..4c1c8735d6c 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -21,6 +21,9 @@ import ( type saveVendors func(uint16, uint16, api.VendorList) type VendorListFetcher func(ctx context.Context, specVersion uint16, listVersion uint16) (vendorlist.VendorList, error) +var cacheSave func(specVersion, listVersion uint16, list api.VendorList) +var cacheLoad func(specVersion, listVersion uint16) api.VendorList + // This file provides the vendorlist-fetching function for Prebid Server. // // For more info, see https://github.com/prebid/prebid-server/issues/504 @@ -28,7 +31,7 @@ type VendorListFetcher func(ctx context.Context, specVersion uint16, listVersion // Nothing in this file is exported. Public APIs can be found in gdpr.go func NewVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16, uint16) string) VendorListFetcher { - cacheSave, cacheLoad := newVendorListCache() + cacheSave, cacheLoad = newVendorListCache() preloadContext, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout()) defer cancel() diff --git a/gdpr/vendorlist-scheduler.go b/gdpr/vendorlist-scheduler.go new file mode 100644 index 00000000000..8dc68b06a4f --- /dev/null +++ b/gdpr/vendorlist-scheduler.go @@ -0,0 +1,132 @@ +package gdpr + +import ( + "context" + "errors" + "net/http" + "sync" + "time" + + "github.com/golang/glog" +) + +type vendorListScheduler struct { + ticker *time.Ticker + interval time.Duration + done chan bool + isRunning bool + isStarted bool + lastRun time.Time + + httpClient *http.Client + timeout time.Duration +} + +// Only single instance must be created +var _instance *vendorListScheduler +var once sync.Once + +func GetVendorListScheduler(interval, timeout string, httpClient *http.Client) (*vendorListScheduler, error) { + if _instance != nil { + return _instance, nil + } + + intervalDuration, err := time.ParseDuration(interval) + if err != nil { + return nil, errors.New("error parsing vendor list scheduler interval: " + err.Error()) + } + + timeoutDuration, err := time.ParseDuration(timeout) + if err != nil { + return nil, errors.New("error parsing vendor list scheduler timeout: " + err.Error()) + } + + if httpClient == nil { + return nil, errors.New("http-client can not be nil") + } + + once.Do(func() { + _instance = &vendorListScheduler{ + ticker: nil, + interval: intervalDuration, + done: make(chan bool), + httpClient: httpClient, + timeout: timeoutDuration, + } + }) + + return _instance, nil +} + +func (scheduler *vendorListScheduler) Start() { + if scheduler == nil || scheduler.isStarted { + return + } + + scheduler.ticker = time.NewTicker(scheduler.interval) + scheduler.isStarted = true + go func() { + for { + select { + case <-scheduler.done: + scheduler.isRunning = false + scheduler.isStarted = false + scheduler.ticker = nil + return + case t := <-scheduler.ticker.C: + if !scheduler.isRunning { + scheduler.isRunning = true + + glog.Info("Running vendor list scheduler at ", t) + scheduler.runLoadCache() + + scheduler.lastRun = t + scheduler.isRunning = false + } + } + } + }() +} + +func (scheduler *vendorListScheduler) Stop() { + if scheduler == nil || !scheduler.isStarted { + return + } + scheduler.ticker.Stop() + scheduler.done <- true +} + +func (scheduler *vendorListScheduler) runLoadCache() { + if scheduler == nil { + return + } + + preloadContext, cancel := context.WithTimeout(context.Background(), scheduler.timeout) + defer cancel() + + versions := [2]struct { + specVersion uint16 + firstListVersion uint16 + }{ + { + specVersion: 2, + firstListVersion: 2, // The GVL for TCF2 has no vendors defined in its first version. It's very unlikely to be used, so don't preload it. + }, + { + specVersion: 3, + firstListVersion: 1, + }, + } + for _, v := range versions { + latestVersion := saveOne(preloadContext, scheduler.httpClient, VendorListURLMaker(v.specVersion, 0), cacheSave) + + for i := latestVersion; i >= v.firstListVersion; i-- { + // Check if version is present in the cache + if list := cacheLoad(v.specVersion, i); list != nil { + continue + } + glog.Infof("Downloading: " + VendorListURLMaker(v.specVersion, i)) + saveOne(preloadContext, scheduler.httpClient, VendorListURLMaker(v.specVersion, i), cacheSave) + } + } +} diff --git a/gdpr/vendorlist-scheduler_test.go b/gdpr/vendorlist-scheduler_test.go new file mode 100644 index 00000000000..f8eaa3cc111 --- /dev/null +++ b/gdpr/vendorlist-scheduler_test.go @@ -0,0 +1,212 @@ +package gdpr + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/prebid/go-gdpr/api" + "github.com/stretchr/testify/assert" +) + +func TestGetVendorListScheduler(t *testing.T) { + type args struct { + interval string + timeout string + httpClient *http.Client + } + tests := []struct { + name string + args args + want *vendorListScheduler + wantErr bool + }{ + { + name: "Test singleton", + args: args{ + interval: "1m", + timeout: "1s", + httpClient: http.DefaultClient, + }, + want: GetExpectedVendorListScheduler("1m", "1s", http.DefaultClient), + wantErr: false, + }, + { + name: "Test singleton again", + args: args{ + interval: "2m", + timeout: "2s", + httpClient: http.DefaultClient, + }, + want: GetExpectedVendorListScheduler("2m", "2s", http.DefaultClient), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + //Mark instance as nil for recreating new instance + if tt.want == nil { + //_instance = nil + } + + got, err := GetVendorListScheduler(tt.args.interval, tt.args.timeout, tt.args.httpClient) + if got != tt.want { + t.Errorf("GetVendorListScheduler() got = %v, want %v", got, tt.want) + } + if (err != nil) != tt.wantErr { + t.Errorf("GetVendorListScheduler() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + +func GetExpectedVendorListScheduler(interval string, timeout string, httpClient *http.Client) *vendorListScheduler { + s, _ := GetVendorListScheduler(interval, timeout, httpClient) + return s +} + +func Test_vendorListScheduler_Start(t *testing.T) { + type fields struct { + scheduler *vendorListScheduler + } + tests := []struct { + name string + fields fields + }{ + { + name: "Start test", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + scheduler, err := GetVendorListScheduler("1m", "30s", http.DefaultClient) + assert.Nil(t, err, "error should be nil") + assert.NotNil(t, scheduler, "scheduler instance should not be nil") + + scheduler.Start() + + assert.NotNil(t, scheduler.ticker, "ticker should not be nil") + assert.True(t, scheduler.isStarted, "isStarted should be true") + + scheduler.Stop() + }) + } +} + +func Test_vendorListScheduler_Stop(t *testing.T) { + type fields struct { + scheduler *vendorListScheduler + } + tests := []struct { + name string + fields fields + }{ + { + name: "Stop test", + }, + { + name: "Calling stop again", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + scheduler, err := GetVendorListScheduler("1m", "30s", http.DefaultClient) + assert.Nil(t, err, "error should be nil") + assert.NotNil(t, scheduler, "scheduler instance should not be nil") + + scheduler.Start() + scheduler.Stop() + + assert.Nil(t, scheduler.ticker, "ticker should not be nil") + assert.False(t, scheduler.isStarted, "isStarted should be true") + }) + } +} + +func Test_vendorListScheduler_runLoadCache(t *testing.T) { + type fields struct { + scheduler *vendorListScheduler + } + tests := []struct { + name string + fields fields + }{ + { + name: "runLoadCache caches all files", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var err error + tt.fields.scheduler, err = GetVendorListScheduler("5m", "5m", http.DefaultClient) + assert.Nil(t, err, "error should be nil") + assert.False(t, tt.fields.scheduler.isStarted, "VendorListScheduler should not be already running") + + tt.fields.scheduler.timeout = 2 * time.Minute + mockCacheSave := func(uint16, uint16, api.VendorList) {} + cacheSave, cacheLoad = newVendorListCache() // initialise global func variables + + tt.fields.scheduler.runLoadCache() + + versions := [2]struct { + specVersion uint16 + firstListVersion uint16 + }{ + { + specVersion: 2, + firstListVersion: 2, + }, + { + specVersion: 3, + firstListVersion: 1, + }, + } + for _, v := range versions { + // saveOne uses mockCacheSave hence it will not save data into actual-cache + latestVersion := saveOne(context.Background(), http.DefaultClient, VendorListURLMaker(v.specVersion, 0), mockCacheSave) + assert.NotEqual(t, latestVersion, 0, "saveOne function returned 0 for version-[%+v]", v) + for i := latestVersion; i >= v.firstListVersion; i-- { + // Check if version is present in the cache + list := cacheLoad(v.specVersion, i) + assert.NotNilf(t, list, "vendor-list file should be present in cache for versions-[%+v]", v) + } + } + }) + } +} + +func Benchmark_vendorListScheduler_runLoadCache(b *testing.B) { + scheduler, err := GetVendorListScheduler("1m", "30m", http.DefaultClient) + assert.Nil(b, err, "") + assert.NotNil(b, scheduler, "") + + scheduler.timeout = 2 * time.Minute + + for n := 0; n < b.N; n++ { + cacheSave, cacheLoad = newVendorListCache() + scheduler.runLoadCache() + } + +} + +func Test_vendorListScheduler_cacheFuncs(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ + vendorListLatestVersion: 1, + vendorLists: map[int]map[int]string{ + 2: { + 1: vendorList1, + 2: vendorList2, + }, + }, + }))) + defer server.Close() + config := testConfig() + + _ = NewVendorListFetcher(context.Background(), config, server.Client(), testURLMaker(server)) + + assert.NotNil(t, cacheSave, "Error gdpr.cacheSave nil") + assert.NotNil(t, cacheLoad, "Error gdpr.cacheLoad nil") +} diff --git a/go.mod b/go.mod index ac394a6742c..a37d41938aa 100644 --- a/go.mod +++ b/go.mod @@ -1,33 +1,39 @@ -module github.com/prebid/prebid-server/v2 +module github.com/PubMatic-OpenWrap/prebid-server/v2 go 1.20 +replace git.pubmatic.com/vastunwrap => git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20231102070946-3c5a3bc1dff5 + require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/IABTechLab/adscert v0.34.0 github.com/NYTimes/gziphandler v1.1.1 github.com/alitto/pond v1.8.3 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d + github.com/beevik/etree v1.0.2 github.com/benbjohnson/clock v1.3.0 github.com/buger/jsonparser v1.1.1 github.com/chasex/glog v0.0.0-20160217080310-c62392af379c github.com/coocood/freecache v1.2.1 github.com/docker/go-units v0.4.0 - github.com/go-sql-driver/mysql v1.6.0 github.com/gofrs/uuid v4.2.0+incompatible github.com/golang/glog v1.1.0 github.com/json-iterator/go v1.1.12 github.com/julienschmidt/httprouter v1.3.0 github.com/lib/pq v1.10.4 + github.com/magiconair/properties v1.8.6 github.com/mitchellh/copystructure v1.2.0 + github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/prebid/go-gdpr v1.12.0 github.com/prebid/go-gpp v0.1.1 github.com/prebid/openrtb/v19 v19.0.0 + github.com/prebid/prebid-server/v2 v2.1.0 github.com/prometheus/client_golang v1.12.1 github.com/prometheus/client_model v0.2.0 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 github.com/rs/cors v1.8.2 + github.com/sergi/go-diff v1.3.1 // indirect github.com/spf13/viper v1.12.0 github.com/stretchr/testify v1.8.1 github.com/vrischmann/go-metrics-influxdb v0.1.1 @@ -41,6 +47,15 @@ require ( ) require ( + git.pubmatic.com/vastunwrap v0.0.0-00010101000000-000000000000 + github.com/go-sql-driver/mysql v1.7.0 + github.com/golang/mock v1.6.0 + github.com/satori/go.uuid v1.2.0 + golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 +) + +require ( + github.com/beevik/etree/110 v0.0.0-00010101000000-000000000000 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -48,7 +63,6 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d // indirect - github.com/magiconair/properties v1.8.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -59,7 +73,6 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/sergi/go-diff v1.2.0 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -69,11 +82,18 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect - github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/crypto v0.14.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/sys v0.14.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) + +replace github.com/prebid/prebid-server/v2 => ./ + +replace github.com/prebid/openrtb/v19 => github.com/PubMatic-OpenWrap/prebid-openrtb/v19 v19.0.0-20230517094918-56ce40c97ced + +replace github.com/beevik/etree v1.0.2 => github.com/PubMatic-OpenWrap/etree v1.0.2-0.20210129100623-8f30cfecf9f4 + +replace github.com/beevik/etree/110 => github.com/beevik/etree v1.1.0 diff --git a/go.sum b/go.sum index 6331beb3287..9929ede3220 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +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= 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= @@ -59,6 +61,10 @@ github.com/IABTechLab/adscert v0.34.0/go.mod h1:pCLd3Up1kfTrH6kYFUGGeavxIc1f6Tvv github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PubMatic-OpenWrap/etree v1.0.2-0.20210129100623-8f30cfecf9f4 h1:EhiijwjoKTx7FVP8p2wwC/z4n5l4c8l2CGmsrFv2uhI= +github.com/PubMatic-OpenWrap/etree v1.0.2-0.20210129100623-8f30cfecf9f4/go.mod h1:5Y8qgcuDoy3XXG907UXkGnVTwihF16rXyJa4zRT7hOE= +github.com/PubMatic-OpenWrap/prebid-openrtb/v19 v19.0.0-20230517094918-56ce40c97ced h1:a4dslMnlBKJTTgBuKKKPT4V43/cespgaVd1y0TO0b4M= +github.com/PubMatic-OpenWrap/prebid-openrtb/v19 v19.0.0-20230517094918-56ce40c97ced/go.mod h1:jK+/g4Dh5vOnNl0Nh7isbZlub29aJYyrtoBkjmhzTIg= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -75,6 +81,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.36.29/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -151,8 +159,8 @@ github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= @@ -176,6 +184,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -382,6 +391,8 @@ github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -402,8 +413,6 @@ github.com/prebid/go-gdpr v1.12.0 h1:OrjQ7Uc+lCRYaOirQ48jjG/PBMvZsKNAaRTgzxN6iZ0 github.com/prebid/go-gdpr v1.12.0/go.mod h1:mPZAdkRxn+iuSjaUuJAi9+0SppBOdM1PCzv/55UH3pY= github.com/prebid/go-gpp v0.1.1 h1:uTMJ+eHmKWL9WvDuxFT4LDoOeJW1yOsfWITqi49ZuY0= github.com/prebid/go-gpp v0.1.1/go.mod h1:b0TLoVln+HXFD9L9xeimxIH3FN8WDKPJ42auslxEkow= -github.com/prebid/openrtb/v19 v19.0.0 h1:NA7okrg7KcvL5wEg6yI0mAyujpyfkC8XSQr3h5ocN88= -github.com/prebid/openrtb/v19 v19.0.0/go.mod h1:jK+/g4Dh5vOnNl0Nh7isbZlub29aJYyrtoBkjmhzTIg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= @@ -442,9 +451,11 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -496,7 +507,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= @@ -542,6 +552,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w= +golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -728,8 +740,8 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/hooks/empty_plan.go b/hooks/empty_plan.go index 514d3824898..e8ae505878a 100644 --- a/hooks/empty_plan.go +++ b/hooks/empty_plan.go @@ -17,6 +17,10 @@ func (e EmptyPlanBuilder) PlanForRawAuctionStage(endpoint string, account *confi return nil } +func (e EmptyPlanBuilder) PlanForValidationStage(endpoint string, account *config.Account) Plan[hookstage.BeforeValidationRequest] { + return nil +} + func (e EmptyPlanBuilder) PlanForProcessedAuctionStage(endpoint string, account *config.Account) Plan[hookstage.ProcessedAuctionRequest] { return nil } diff --git a/hooks/hookexecution/context.go b/hooks/hookexecution/context.go index 0817078137f..cdfbb163ca0 100644 --- a/hooks/hookexecution/context.go +++ b/hooks/hookexecution/context.go @@ -32,6 +32,8 @@ func (ctx executionContext) getModuleContext(moduleName string) hookstage.Module } moduleInvocationCtx.AccountConfig = cfg + moduleInvocationCtx.AccountID = ctx.account.ID + } return moduleInvocationCtx diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go index f820b79fdb2..c7aa7e4e86e 100644 --- a/hooks/hookexecution/executor.go +++ b/hooks/hookexecution/executor.go @@ -18,6 +18,7 @@ import ( const ( EndpointAuction = "/openrtb2/auction" EndpointAmp = "/openrtb2/amp" + EndpointCtv = "/openrtb/video" ) // An entity specifies the type of object that was processed during the execution of the stage. @@ -38,6 +39,7 @@ type StageExecutor interface { ExecuteRawBidderResponseStage(response *adapters.BidderResponse, bidder string) *RejectError ExecuteAllProcessedBidResponsesStage(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) ExecuteAuctionResponseStage(response *openrtb2.BidResponse) + ExecuteBeforeRequestValidationStage(req *openrtb2.BidRequest) *RejectError } type HookStageExecutor interface { @@ -139,6 +141,35 @@ func (e *hookExecutor) ExecuteRawAuctionStage(requestBody []byte) ([]byte, *Reje return payload, reject } +func (e *hookExecutor) ExecuteBeforeRequestValidationStage(request *openrtb2.BidRequest) *RejectError { + plan := e.planBuilder.PlanForValidationStage(e.endpoint, e.account) + if len(plan) == 0 { + return nil + } + + handler := func( + ctx context.Context, + moduleCtx hookstage.ModuleInvocationContext, + hook hookstage.BeforeValidationRequest, + payload hookstage.BeforeValidationRequestPayload, + ) (hookstage.HookResult[hookstage.BeforeValidationRequestPayload], error) { + return hook.HandleBeforeValidationHook(ctx, moduleCtx, payload) + } + + stageName := hooks.StageBeforeValidationRequest.String() + executionCtx := e.newContext(stageName) + payload := hookstage.BeforeValidationRequestPayload{BidRequest: request} + + outcome, _, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) + outcome.Entity = entityAuctionRequest + outcome.Stage = stageName + + e.saveModuleContexts(contexts) + e.pushStageOutcome(outcome) + + return reject +} + func (e *hookExecutor) ExecuteProcessedAuctionStage(request *openrtb_ext.RequestWrapper) error { plan := e.planBuilder.PlanForProcessedAuctionStage(e.endpoint, e.account) if len(plan) == 0 { @@ -344,3 +375,7 @@ func (executor EmptyHookExecutor) ExecuteAllProcessedBidResponsesStage(_ map[ope } func (executor EmptyHookExecutor) ExecuteAuctionResponseStage(_ *openrtb2.BidResponse) {} + +func (executor *EmptyHookExecutor) ExecuteBeforeRequestValidationStage(_ *openrtb2.BidRequest) *RejectError { + return nil +} diff --git a/hooks/hookexecution/executor_test.go b/hooks/hookexecution/executor_test.go index 90fa09e394f..ceb747ada1d 100644 --- a/hooks/hookexecution/executor_test.go +++ b/hooks/hookexecution/executor_test.go @@ -2083,6 +2083,17 @@ func (e TestApplyHookMutationsBuilder) PlanForRawAuctionStage(_ string, _ *confi } } +func (e TestApplyHookMutationsBuilder) PlanForValidationStage(_ string, _ *config.Account) hooks.Plan[hookstage.BeforeValidationRequest] { + return hooks.Plan[hookstage.BeforeValidationRequest]{ + hooks.Group[hookstage.BeforeValidationRequest]{ + Timeout: 10 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.BeforeValidationRequest]{ + {Module: "foobar", Code: "foo", Hook: mockUpdateBidRequestHook{}}, + }, + }, + } +} + func (e TestApplyHookMutationsBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { return hooks.Plan[hookstage.ProcessedAuctionRequest]{ hooks.Group[hookstage.ProcessedAuctionRequest]{ diff --git a/hooks/hookexecution/mocks_test.go b/hooks/hookexecution/mocks_test.go index c47e2892cc0..eb0496817b3 100644 --- a/hooks/hookexecution/mocks_test.go +++ b/hooks/hookexecution/mocks_test.go @@ -299,6 +299,23 @@ func (h mockFailedMutationHook) HandleAllProcessedBidResponsesHook(_ context.Con return hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload]{ChangeSet: changeSet}, nil } +func (e mockUpdateBidRequestHook) HandleBeforeValidationHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.BeforeValidationRequestPayload) (hookstage.HookResult[hookstage.BeforeValidationRequestPayload], error) { + c := hookstage.ChangeSet[hookstage.BeforeValidationRequestPayload]{} + // c.AddMutation( + // func(payload hookstage.BeforeValidationRequestPayload) (hookstage.BeforeValidationRequestPayload, error) { + // payload.BidRequest.User.Yob = 2000 + // return payload, nil + // }, hookstage.MutationUpdate, "bidRequest", "user.yob", + // ).AddMutation( + // func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { + // payload.BidRequest.User.Consent = "true" + // return payload, nil + // }, hookstage.MutationUpdate, "bidRequest", "user.consent", + // ) + + return hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ChangeSet: c}, nil +} + type mockUpdateBidRequestHook struct{} func (e mockUpdateBidRequestHook) HandleProcessedAuctionHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.ProcessedAuctionRequestPayload) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { diff --git a/hooks/hookstage/invocation.go b/hooks/hookstage/invocation.go index 73b210957e2..d6adb506066 100644 --- a/hooks/hookstage/invocation.go +++ b/hooks/hookstage/invocation.go @@ -23,6 +23,7 @@ type HookResult[T any] struct { type ModuleInvocationContext struct { // AccountConfig represents module config rewritten at the account-level. AccountConfig json.RawMessage + AccountID string // Endpoint represents the path of the current endpoint. Endpoint string // ModuleContext holds values that the module passes to itself from the previous stages. diff --git a/hooks/hookstage/processedbeforerequestvalidation.go b/hooks/hookstage/processedbeforerequestvalidation.go new file mode 100644 index 00000000000..219d0eed580 --- /dev/null +++ b/hooks/hookstage/processedbeforerequestvalidation.go @@ -0,0 +1,22 @@ +package hookstage + +import ( + "context" + + "github.com/prebid/openrtb/v19/openrtb2" +) + +// BeforeValidationRequest +type BeforeValidationRequest interface { + HandleBeforeValidationHook( + context.Context, + ModuleInvocationContext, + BeforeValidationRequestPayload, + ) (HookResult[BeforeValidationRequestPayload], error) +} + +// ProcessedBeforeRequestValidationPayload consists of the openrtb2.BidRequest object. +// Hooks are allowed to modify openrtb2.BidRequest using mutations. +type BeforeValidationRequestPayload struct { + BidRequest *openrtb2.BidRequest +} diff --git a/hooks/plan.go b/hooks/plan.go index a3a0e9af661..0d119b91b23 100644 --- a/hooks/plan.go +++ b/hooks/plan.go @@ -3,7 +3,6 @@ package hooks import ( "time" - "github.com/golang/glog" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/hooks/hookstage" ) @@ -14,6 +13,7 @@ type Stage string const ( StageEntrypoint Stage = "entrypoint" StageRawAuctionRequest Stage = "raw_auction_request" + StageBeforeValidationRequest Stage = "before_validation_request" StageProcessedAuctionRequest Stage = "processed_auction_request" StageBidderRequest Stage = "bidder_request" StageRawBidderResponse Stage = "raw_bidder_response" @@ -36,6 +36,7 @@ func (s Stage) IsRejectable() bool { type ExecutionPlanBuilder interface { PlanForEntrypointStage(endpoint string) Plan[hookstage.Entrypoint] PlanForRawAuctionStage(endpoint string, account *config.Account) Plan[hookstage.RawAuctionRequest] + PlanForValidationStage(endpoint string, account *config.Account) Plan[hookstage.BeforeValidationRequest] PlanForProcessedAuctionStage(endpoint string, account *config.Account) Plan[hookstage.ProcessedAuctionRequest] PlanForBidderRequestStage(endpoint string, account *config.Account) Plan[hookstage.BidderRequest] PlanForRawBidderResponseStage(endpoint string, account *config.Account) Plan[hookstage.RawBidderResponse] @@ -106,6 +107,16 @@ func (p PlanBuilder) PlanForRawAuctionStage(endpoint string, account *config.Acc ) } +func (p PlanBuilder) PlanForValidationStage(endpoint string, account *config.Account) Plan[hookstage.BeforeValidationRequest] { + return getMergedPlan( + p.hooks, + account, + endpoint, + StageBeforeValidationRequest, + p.repo.GetBeforeValidationHook, + ) +} + func (p PlanBuilder) PlanForProcessedAuctionStage(endpoint string, account *config.Account) Plan[hookstage.ProcessedAuctionRequest] { return getMergedPlan( p.hooks, @@ -197,9 +208,10 @@ func getGroup[T any](getHookFn hookFn[T], cfg config.HookExecutionGroup) Group[T for _, hookCfg := range cfg.HookSequence { if h, ok := getHookFn(hookCfg.ModuleCode); ok { group.Hooks = append(group.Hooks, HookWrapper[T]{Module: hookCfg.ModuleCode, Code: hookCfg.HookImplCode, Hook: h}) - } else { - glog.Warningf("Not found hook while building hook execution plan: %s %s", hookCfg.ModuleCode, hookCfg.HookImplCode) } + // else { + // glog.Warningf("Not found hook while building hook execution plan: %s %s", hookCfg.ModuleCode, hookCfg.HookImplCode) + // } } return group diff --git a/hooks/plan_test.go b/hooks/plan_test.go index 064403cb8cf..6ce030ec550 100644 --- a/hooks/plan_test.go +++ b/hooks/plan_test.go @@ -821,6 +821,16 @@ func (f fakeRawAuctionHook) HandleRawAuctionHook( return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{}, nil } +type fakeBeforeValidationHooks struct{} + +func (f fakeBeforeValidationHooks) HandleBeforeValidationHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.BeforeValidationRequestPayload, +) (hookstage.HookResult[hookstage.BeforeValidationRequestPayload], error) { + return hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{}, nil +} + type fakeProcessedAuctionHook struct{} func (f fakeProcessedAuctionHook) HandleProcessedAuctionHook( diff --git a/hooks/repo.go b/hooks/repo.go index 3d8db581bda..52f838739f0 100644 --- a/hooks/repo.go +++ b/hooks/repo.go @@ -16,6 +16,7 @@ import ( type HookRepository interface { GetEntrypointHook(id string) (hookstage.Entrypoint, bool) GetRawAuctionHook(id string) (hookstage.RawAuctionRequest, bool) + GetBeforeValidationHook(id string) (hookstage.BeforeValidationRequest, bool) GetProcessedAuctionHook(id string) (hookstage.ProcessedAuctionRequest, bool) GetBidderRequestHook(id string) (hookstage.BidderRequest, bool) GetRawBidderResponseHook(id string) (hookstage.RawBidderResponse, bool) @@ -44,6 +45,7 @@ func NewHookRepository(hooks map[string]interface{}) (HookRepository, error) { type hookRepository struct { entrypointHooks map[string]hookstage.Entrypoint rawAuctionHooks map[string]hookstage.RawAuctionRequest + beforeValidationHooks map[string]hookstage.BeforeValidationRequest processedAuctionHooks map[string]hookstage.ProcessedAuctionRequest bidderRequestHooks map[string]hookstage.BidderRequest rawBidderResponseHooks map[string]hookstage.RawBidderResponse @@ -59,6 +61,10 @@ func (r *hookRepository) GetRawAuctionHook(id string) (hookstage.RawAuctionReque return getHook(r.rawAuctionHooks, id) } +func (r *hookRepository) GetBeforeValidationHook(id string) (hookstage.BeforeValidationRequest, bool) { + return getHook(r.beforeValidationHooks, id) +} + func (r *hookRepository) GetProcessedAuctionHook(id string) (hookstage.ProcessedAuctionRequest, bool) { return getHook(r.processedAuctionHooks, id) } @@ -97,6 +103,13 @@ func (r *hookRepository) add(id string, hook interface{}) error { } } + if h, ok := hook.(hookstage.BeforeValidationRequest); ok { + hasAnyHooks = true + if r.beforeValidationHooks, err = addHook(r.beforeValidationHooks, h, id); err != nil { + return err + } + } + if h, ok := hook.(hookstage.ProcessedAuctionRequest); ok { hasAnyHooks = true if r.processedAuctionHooks, err = addHook(r.processedAuctionHooks, h, id); err != nil { diff --git a/main.go b/main.go index e72f02f1f0e..53c74480601 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,4 @@ -package main +package main_ow import ( "flag" @@ -23,7 +23,8 @@ func init() { rand.Seed(time.Now().UnixNano()) } -func main() { +// TODO: revert this after PBS-OpenWrap module +func Main() { flag.Parse() // required for glog flags and testing package flags bidderInfoPath, err := filepath.Abs(infoDirectory) @@ -54,7 +55,7 @@ func main() { } } -const configFileName = "pbs" +const configFileName = "pbs.yaml" const infoDirectory = "./static/bidder-info" func loadConfig(bidderInfos config.BidderInfos) (*config.Configuration, error) { diff --git a/main_test.go b/main_test.go index 79ae373d473..0969b5e71ef 100644 --- a/main_test.go +++ b/main_test.go @@ -1,4 +1,4 @@ -package main +package main_ow import ( "os" diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 7ed387512fd..7760a865b57 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -3,17 +3,20 @@ package config import ( "time" + "github.com/golang/glog" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/metrics" prometheusmetrics "github.com/prebid/prebid-server/v2/metrics/prometheus" "github.com/prebid/prebid-server/v2/openrtb_ext" + + "github.com/prometheus/client_golang/prometheus" gometrics "github.com/rcrowley/go-metrics" influxdb "github.com/vrischmann/go-metrics-influxdb" ) // NewMetricsEngine reads the configuration and returns the appropriate metrics engine // for this instance. -func NewMetricsEngine(cfg *config.Configuration, adapterList []openrtb_ext.BidderName, syncerKeys []string, moduleStageNames map[string][]string) *DetailedMetricsEngine { +func NewMetricsEngine(cfg *config.Configuration, metricsRegistry MetricsRegistry, adapterList []openrtb_ext.BidderName, syncerKeys []string, moduleStageNames map[string][]string) *DetailedMetricsEngine { // Create a list of metrics engines to use. // Capacity of 2, as unlikely to have more than 2 metrics backends, and in the case // of 1 we won't use the list so it will be garbage collected. @@ -25,22 +28,36 @@ func NewMetricsEngine(cfg *config.Configuration, adapterList []openrtb_ext.Bidde returnEngine.GoMetrics = metrics.NewMetrics(gometrics.NewPrefixedRegistry("prebidserver."), adapterList, cfg.Metrics.Disabled, syncerKeys, moduleStageNames) engineList = append(engineList, returnEngine.GoMetrics) + // Get the registry for influxdb + influxRegistry, ok := metricsRegistry[InfluxRegistry].(gometrics.Registry) + if !ok || influxRegistry == nil { + glog.Info("Failed to get the influxRegistry from MetricsRegistry, creating new instance of influx registry.") + influxRegistry = gometrics.NewPrefixedRegistry("prebidserver.") + } + // Set up the Influx logger go influxdb.InfluxDB( - returnEngine.GoMetrics.MetricsRegistry, // metrics registry + influxRegistry, // metrics registry time.Second*time.Duration(cfg.Metrics.Influxdb.MetricSendInterval), // Configurable interval - cfg.Metrics.Influxdb.Host, // the InfluxDB url - cfg.Metrics.Influxdb.Database, // your InfluxDB database - cfg.Metrics.Influxdb.Measurement, // your measurement - cfg.Metrics.Influxdb.Username, // your InfluxDB user - cfg.Metrics.Influxdb.Password, // your InfluxDB password, - cfg.Metrics.Influxdb.AlignTimestamps, // align timestamps + cfg.Metrics.Influxdb.Host, // the InfluxDB url + cfg.Metrics.Influxdb.Database, // your InfluxDB database + cfg.Metrics.Influxdb.Measurement, // your measurement + cfg.Metrics.Influxdb.Username, // your InfluxDB user + cfg.Metrics.Influxdb.Password, // your InfluxDB password, + cfg.Metrics.Influxdb.AlignTimestamps, // align timestamps ) // Influx is not added to the engine list as goMetrics takes care of it already. } if cfg.Metrics.Prometheus.Port != 0 { + // Get the prometheus registry + prometheusRegistry, ok := metricsRegistry[PrometheusRegistry].(*prometheus.Registry) + if !ok || prometheusRegistry == nil { + glog.Info("Failed to get the prometheusRegistry from MetricsRegistry, creating new instance of prometheus registry.") + prometheusRegistry = prometheus.NewRegistry() + } + // Set up the Prometheus metrics. - returnEngine.PrometheusMetrics = prometheusmetrics.NewMetrics(cfg.Metrics.Prometheus, cfg.Metrics.Disabled, syncerKeys, moduleStageNames) + returnEngine.PrometheusMetrics = prometheusmetrics.NewMetrics(cfg.Metrics.Prometheus, prometheusRegistry, cfg.Metrics.Disabled, syncerKeys, moduleStageNames) engineList = append(engineList, returnEngine.PrometheusMetrics) } @@ -149,9 +166,9 @@ func (me *MultiMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) { } } -func (me *MultiMetricsEngine) RecordTLSHandshakeTime(tlsHandshakeTime time.Duration) { +func (me *MultiMetricsEngine) RecordTLSHandshakeTime(adapterName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) { for _, thisME := range *me { - thisME.RecordTLSHandshakeTime(tlsHandshakeTime) + thisME.RecordTLSHandshakeTime(adapterName, tlsHandshakeTime) } } @@ -266,6 +283,48 @@ func (me *MultiMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels } } +// RecordAdapterDuplicateBidID across all engines +func (me *MultiMetricsEngine) RecordAdapterDuplicateBidID(adaptor string, collisions int) { + for _, thisME := range *me { + thisME.RecordAdapterDuplicateBidID(adaptor, collisions) + } +} + +// RecordRequestHavingDuplicateBidID across all engines +func (me *MultiMetricsEngine) RecordRequestHavingDuplicateBidID() { + for _, thisME := range *me { + thisME.RecordRequestHavingDuplicateBidID() + } +} + +// RecordPodImpGenTime across all engines +func (me *MultiMetricsEngine) RecordPodImpGenTime(labels metrics.PodLabels, startTime time.Time) { + for _, thisME := range *me { + thisME.RecordPodImpGenTime(labels, startTime) + } +} + +// RecordPodCombGenTime as a noop +func (me *MultiMetricsEngine) RecordPodCombGenTime(labels metrics.PodLabels, elapsedTime time.Duration) { + for _, thisME := range *me { + thisME.RecordPodCombGenTime(labels, elapsedTime) + } +} + +// RecordPodCompititveExclusionTime as a noop +func (me *MultiMetricsEngine) RecordPodCompititveExclusionTime(labels metrics.PodLabels, elapsedTime time.Duration) { + for _, thisME := range *me { + thisME.RecordPodCompititveExclusionTime(labels, elapsedTime) + } +} + +// RecordAdapterVideoBidDuration as a noop +func (me *MultiMetricsEngine) RecordAdapterVideoBidDuration(labels metrics.AdapterLabels, videoBidDuration int) { + for _, thisME := range *me { + thisME.RecordAdapterVideoBidDuration(labels, videoBidDuration) + } +} + // RecordAdapterGDPRRequestBlocked across all engines func (me *MultiMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) { for _, thisME := range *me { @@ -286,6 +345,18 @@ func (me *MultiMetricsEngine) RecordStoredResponse(pubId string) { } } +func (me *MultiMetricsEngine) RecordRejectedBidsForAccount(pubId string) { + for _, thisME := range *me { + thisME.RecordRejectedBidsForAccount(pubId) + } +} + +func (me *MultiMetricsEngine) RecordFloorsRequestForAccount(pubId string) { + for _, thisME := range *me { + thisME.RecordFloorsRequestForAccount(pubId) + } +} + func (me *MultiMetricsEngine) RecordAdsCertReq(success bool) { for _, thisME := range *me { thisME.RecordAdsCertReq(success) @@ -368,6 +439,24 @@ func (me *MultiMetricsEngine) RecordModuleTimeout(labels metrics.ModuleLabels) { // used if no metric backend is configured and also for tests. type NilMetricsEngine struct{} +func (me *NilMetricsEngine) RecordAdapterDuplicateBidID(adaptor string, collisions int) { +} + +func (me *NilMetricsEngine) RecordRequestHavingDuplicateBidID() { +} + +func (me *NilMetricsEngine) RecordPodImpGenTime(labels metrics.PodLabels, startTime time.Time) { +} + +func (me *NilMetricsEngine) RecordPodCombGenTime(labels metrics.PodLabels, elapsedTime time.Duration) { +} + +func (me *NilMetricsEngine) RecordPodCompititveExclusionTime(labels metrics.PodLabels, elapsedTime time.Duration) { +} + +func (me *NilMetricsEngine) RecordAdapterVideoBidDuration(labels metrics.AdapterLabels, videoBidDuration int) { +} + // RecordRequest as a noop func (me *NilMetricsEngine) RecordRequest(labels metrics.Labels) { } @@ -417,7 +506,7 @@ func (me *NilMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) { } // RecordTLSHandshakeTime as a noop -func (me *NilMetricsEngine) RecordTLSHandshakeTime(tlsHandshakeTime time.Duration) { +func (me *NilMetricsEngine) RecordTLSHandshakeTime(adapterName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) { } // RecordBidderServerResponseTime as a noop @@ -495,6 +584,12 @@ func (me *NilMetricsEngine) RecordDebugRequest(debugEnabled bool, pubId string) func (me *NilMetricsEngine) RecordStoredResponse(pubId string) { } +func (me *NilMetricsEngine) RecordRejectedBidsForAccount(pubId string) { +} + +func (me *NilMetricsEngine) RecordFloorsRequestForAccount(pubId string) { +} + func (me *NilMetricsEngine) RecordAdsCertReq(success bool) { } diff --git a/metrics/config/metrics_ow.go b/metrics/config/metrics_ow.go new file mode 100644 index 00000000000..62a950438cd --- /dev/null +++ b/metrics/config/metrics_ow.go @@ -0,0 +1,90 @@ +package config + +import ( + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prometheus/client_golang/prometheus" + gometrics "github.com/rcrowley/go-metrics" +) + +type RegistryType = string +type MetricsRegistry map[RegistryType]interface{} + +const ( + PrometheusRegistry RegistryType = "prometheus" + InfluxRegistry RegistryType = "influx" +) + +// NewMetricsRegistry returns the map of metrics-engine-name and its respective registry +func NewMetricsRegistry() MetricsRegistry { + return MetricsRegistry{ + PrometheusRegistry: prometheus.NewRegistry(), + InfluxRegistry: gometrics.NewPrefixedRegistry("prebidserver."), + } +} + +func (me *MultiMetricsEngine) RecordVASTTagType(biddder, vastTag string) { + for _, thisME := range *me { + thisME.RecordVASTTagType(biddder, vastTag) + } +} + +func (me *MultiMetricsEngine) RecordRejectedBids(pubid, bidder, code string) { + for _, thisME := range *me { + thisME.RecordRejectedBids(pubid, bidder, code) + } +} + +func (me *MultiMetricsEngine) RecordBids(pubid, profileid, biddder, deal string) { + for _, thisME := range *me { + thisME.RecordBids(pubid, profileid, biddder, deal) + } +} +func (me *MultiMetricsEngine) RecordHttpCounter() { +} + +func (me *MultiMetricsEngine) RecordVastVersion(biddder, vastVersion string) { + for _, thisME := range *me { + thisME.RecordVastVersion(biddder, vastVersion) + } +} + +// RecordRejectedBidsForBidder across all engines +func (me *MultiMetricsEngine) RecordRejectedBidsForBidder(bidder openrtb_ext.BidderName) { + for _, thisME := range *me { + thisME.RecordRejectedBidsForBidder(bidder) + } +} + +// RecordDynamicFetchFailure across all engines +func (me *MultiMetricsEngine) RecordDynamicFetchFailure(pubId, code string) { + for _, thisME := range *me { + thisME.RecordDynamicFetchFailure(pubId, code) + } +} + +// RecordVASTTagType as a noop +func (me *NilMetricsEngine) RecordVASTTagType(biddder, vastTag string) { +} + +// RecordDynamicFetchFailure as a noop +func (me *NilMetricsEngine) RecordDynamicFetchFailure(pubId, code string) { +} + +// RecordRejectedBids as a noop +func (me *NilMetricsEngine) RecordRejectedBids(pubid, bidder, code string) { +} + +// RecordBids as a noop +func (me *NilMetricsEngine) RecordBids(pubid, profileid, biddder, deal string) { +} + +// RecordVastVersion as a noop +func (me *NilMetricsEngine) RecordVastVersion(biddder, vastVersion string) { +} + +func (m *NilMetricsEngine) RecordHttpCounter() { +} + +// RecordRejectedBidsForBidder as a noop +func (me *NilMetricsEngine) RecordRejectedBidsForBidder(bidder openrtb_ext.BidderName) { +} diff --git a/metrics/config/metrics_ow_test.go b/metrics/config/metrics_ow_test.go new file mode 100644 index 00000000000..c34cfd703cf --- /dev/null +++ b/metrics/config/metrics_ow_test.go @@ -0,0 +1,82 @@ +package config + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + prometheusmetrics "github.com/prebid/prebid-server/v2/metrics/prometheus" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestGoMetricsEngineForNilRegistry(t *testing.T) { + cfg := config.Configuration{} + cfg.Metrics.Influxdb.Host = "localhost" + adapterList := make([]openrtb_ext.BidderName, 0, 2) + syncerKeys := []string{"keyA", "keyB"} + testEngine := NewMetricsEngine(&cfg, nil, adapterList, syncerKeys, modulesStages) + _, ok := testEngine.MetricsEngine.(*metrics.Metrics) + if !ok { + t.Error("Expected a Metrics as MetricsEngine, but didn't get it") + } +} + +func TestPrometheusMetricsEngine(t *testing.T) { + + adapterList := make([]openrtb_ext.BidderName, 0, 2) + syncerKeys := []string{"keyA", "keyB"} + + type args struct { + cfg *config.Configuration + metricsRegistry MetricsRegistry + } + testCases := []struct { + name string + args args + }{ + { + name: "nil_prometheus_registry", + args: args{ + cfg: &config.Configuration{ + Metrics: config.Metrics{ + Prometheus: config.PrometheusMetrics{ + Port: 9090, + Namespace: "ow", + Subsystem: "pbs", + TimeoutMillisRaw: 5, + }, + }, + }, + metricsRegistry: MetricsRegistry{ + PrometheusRegistry: nil, + }, + }, + }, + { + name: "valid_prometheus_registry", + args: args{ + cfg: &config.Configuration{ + Metrics: config.Metrics{ + Prometheus: config.PrometheusMetrics{ + Port: 9090, + Namespace: "ow", + Subsystem: "pbs", + TimeoutMillisRaw: 5, + }, + }, + }, + metricsRegistry: NewMetricsRegistry(), + }, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + testEngine := NewMetricsEngine(test.args.cfg, test.args.metricsRegistry, adapterList, syncerKeys, modulesStages) + _, ok := testEngine.MetricsEngine.(*prometheusmetrics.Metrics) + if !ok { + t.Error("Expected a Metrics as MetricsEngine, but didn't get it") + } + }) + } +} diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index 5badc348e61..3e666e21a24 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -20,7 +20,7 @@ func TestNilMetricsEngine(t *testing.T) { cfg := mainConfig.Configuration{} adapterList := make([]openrtb_ext.BidderName, 0, 2) syncerKeys := []string{"keyA", "keyB"} - testEngine := NewMetricsEngine(&cfg, adapterList, syncerKeys, modulesStages) + testEngine := NewMetricsEngine(&cfg, nil, adapterList, syncerKeys, modulesStages) _, ok := testEngine.MetricsEngine.(*NilMetricsEngine) if !ok { t.Error("Expected a NilMetricsEngine, but didn't get it") @@ -32,7 +32,7 @@ func TestGoMetricsEngine(t *testing.T) { cfg.Metrics.Influxdb.Host = "localhost" adapterList := make([]openrtb_ext.BidderName, 0, 2) syncerKeys := []string{"keyA", "keyB"} - testEngine := NewMetricsEngine(&cfg, adapterList, syncerKeys, modulesStages) + testEngine := NewMetricsEngine(&cfg, NewMetricsRegistry(), adapterList, syncerKeys, modulesStages) _, ok := testEngine.MetricsEngine.(*metrics.Metrics) if !ok { t.Error("Expected a Metrics as MetricsEngine, but didn't get it") diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 5bc4ab965f6..3dda6d4df2f 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -37,14 +37,15 @@ type Metrics struct { StoredResponsesMeter metrics.Meter // Metrics for OpenRTB requests specifically - RequestStatuses map[RequestType]map[RequestStatus]metrics.Meter - AmpNoCookieMeter metrics.Meter - CookieSyncMeter metrics.Meter - CookieSyncStatusMeter map[CookieSyncStatus]metrics.Meter - SyncerRequestsMeter map[string]map[SyncerCookieSyncStatus]metrics.Meter - SetUidMeter metrics.Meter - SetUidStatusMeter map[SetUidStatus]metrics.Meter - SyncerSetsMeter map[string]map[SyncerSetUidStatus]metrics.Meter + RequestStatuses map[RequestType]map[RequestStatus]metrics.Meter + AmpNoCookieMeter metrics.Meter + CookieSyncMeter metrics.Meter + CookieSyncStatusMeter map[CookieSyncStatus]metrics.Meter + SyncerRequestsMeter map[string]map[SyncerCookieSyncStatus]metrics.Meter + SetUidMeter metrics.Meter + SetUidStatusMeter map[SetUidStatus]metrics.Meter + SyncerSetsMeter map[string]map[SyncerSetUidStatus]metrics.Meter + FloorRejectedBidsMeter map[openrtb_ext.BidderName]metrics.Meter // Media types found in the "imp" JSON object ImpsTypeBanner metrics.Meter @@ -63,6 +64,20 @@ type Metrics struct { PrivacyLMTRequest metrics.Meter PrivacyTCFRequestVersion map[TCFVersionValue]metrics.Meter + // Ad Pod Metrics + + // podImpGenTimer indicates time taken by impression generator + // algorithm to generate impressions for given ad pod request + podImpGenTimer metrics.Timer + + // podImpGenTimer indicates time taken by combination generator + // algorithm to generate combination based on bid response and ad pod request + podCombGenTimer metrics.Timer + + // podCompExclTimer indicates time taken by compititve exclusion + // algorithm to generate final pod response based on bid response and ad pod request + podCompExclTimer metrics.Timer + AdapterMetrics map[string]*AdapterMetrics // Don't export accountMetrics because we need helper functions here to insure its properly populated dynamically accountMetrics map[string]*accountMetrics @@ -106,6 +121,7 @@ type AdapterMetrics struct { BidValidationSecureMarkupErrorMeter metrics.Meter BidValidationSecureMarkupWarnMeter metrics.Meter + TLSHandshakeTimer metrics.Timer } type MarkupDeliveryMetrics struct { @@ -114,10 +130,12 @@ type MarkupDeliveryMetrics struct { } type accountMetrics struct { - requestMeter metrics.Meter - debugRequestMeter metrics.Meter - bidsReceivedMeter metrics.Meter - priceHistogram metrics.Histogram + requestMeter metrics.Meter + rejecteBidMeter metrics.Meter + floorsRequestMeter metrics.Meter + debugRequestMeter metrics.Meter + bidsReceivedMeter metrics.Meter + priceHistogram metrics.Histogram // store account by adapter metrics. Type is map[PBSBidder.BidderCode] adapterMetrics map[string]*AdapterMetrics moduleMetrics map[string]*ModuleMetrics @@ -127,6 +145,22 @@ type accountMetrics struct { bidValidationCreativeSizeWarnMeter metrics.Meter bidValidationSecureMarkupMeter metrics.Meter bidValidationSecureMarkupWarnMeter metrics.Meter + + // Account Deprciation Metrics + accountDeprecationWarningsPurpose1Meter metrics.Meter + accountDeprecationWarningsPurpose2Meter metrics.Meter + accountDeprecationWarningsPurpose3Meter metrics.Meter + accountDeprecationWarningsPurpose4Meter metrics.Meter + accountDeprecationWarningsPurpose5Meter metrics.Meter + accountDeprecationWarningsPurpose6Meter metrics.Meter + accountDeprecationWarningsPurpose7Meter metrics.Meter + accountDeprecationWarningsPurpose8Meter metrics.Meter + accountDeprecationWarningsPurpose9Meter metrics.Meter + accountDeprecationWarningsPurpose10Meter metrics.Meter + channelEnabledGDPRMeter metrics.Meter + channelEnabledCCPAMeter metrics.Meter + accountDeprecationSummaryMeter metrics.Meter + dynamicFetchFailureMeter metrics.Meter } type ModuleMetrics struct { @@ -163,7 +197,6 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []string, disabledMetr NoCookieMeter: blankMeter, RequestTimer: blankTimer, DNSLookupTimer: blankTimer, - TLSHandshakeTimer: blankTimer, RequestsQueueTimer: make(map[RequestType]map[bool]metrics.Timer), PrebidCacheRequestTimerSuccess: blankTimer, PrebidCacheRequestTimerError: blankTimer, @@ -180,6 +213,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []string, disabledMetr SetUidStatusMeter: make(map[SetUidStatus]metrics.Meter), SyncerSetsMeter: make(map[string]map[SyncerSetUidStatus]metrics.Meter), StoredResponsesMeter: blankMeter, + FloorRejectedBidsMeter: make(map[openrtb_ext.BidderName]metrics.Meter), ImpsTypeBanner: blankMeter, ImpsTypeVideo: blankMeter, @@ -295,7 +329,6 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.DebugRequestMeter = metrics.GetOrRegisterMeter("debug_requests", registry) newMetrics.RequestTimer = metrics.GetOrRegisterTimer("request_time", registry) newMetrics.DNSLookupTimer = metrics.GetOrRegisterTimer("dns_lookup_time", registry) - newMetrics.TLSHandshakeTimer = metrics.GetOrRegisterTimer("tls_handshake_time", registry) newMetrics.PrebidCacheRequestTimerSuccess = metrics.GetOrRegisterTimer("prebid_cache_request_time.ok", registry) newMetrics.PrebidCacheRequestTimerError = metrics.GetOrRegisterTimer("prebid_cache_request_time.err", registry) newMetrics.StoredResponsesMeter = metrics.GetOrRegisterMeter("stored_responses", registry) @@ -339,6 +372,7 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d for _, a := range lowerCaseExchanges { registerAdapterMetrics(registry, "adapter", string(a), newMetrics.AdapterMetrics[a]) + newMetrics.FloorRejectedBidsMeter[openrtb_ext.BidderName(a)] = metrics.GetOrRegisterMeter(fmt.Sprintf("rejected_bid.%s", a), registry) } for typ, statusMap := range newMetrics.RequestStatuses { @@ -405,6 +439,7 @@ func makeBlankAdapterMetrics(disabledMetrics config.DisabledMetrics) *AdapterMet newAdapter.ConnCreated = metrics.NilCounter{} newAdapter.ConnReused = metrics.NilCounter{} newAdapter.ConnWaitTime = &metrics.NilTimer{} + newAdapter.TLSHandshakeTimer = &metrics.NilTimer{} } if !disabledMetrics.AdapterGDPRRequestBlocked { newAdapter.GDPRRequestBlocked = blankMeter @@ -477,6 +512,7 @@ func registerAdapterMetrics(registry metrics.Registry, adapterOrAccount string, am.ConnCreated = metrics.GetOrRegisterCounter(fmt.Sprintf("%[1]s.%[2]s.connections_created", adapterOrAccount, exchange), registry) am.ConnReused = metrics.GetOrRegisterCounter(fmt.Sprintf("%[1]s.%[2]s.connections_reused", adapterOrAccount, exchange), registry) am.ConnWaitTime = metrics.GetOrRegisterTimer(fmt.Sprintf("%[1]s.%[2]s.connection_wait_time", adapterOrAccount, exchange), registry) + am.TLSHandshakeTimer = metrics.GetOrRegisterTimer(fmt.Sprintf("%[1]s.%[2]s.tls_handshake_time", adapterOrAccount, exchange), registry) for err := range am.ErrorMeters { am.ErrorMeters[err] = metrics.GetOrRegisterMeter(fmt.Sprintf("%s.%s.requests.%s", adapterOrAccount, exchange, err), registry) } @@ -549,6 +585,9 @@ func (me *Metrics) getAccountMetrics(id string) *accountMetrics { } am = &accountMetrics{} am.requestMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.requests", id), me.MetricsRegistry) + am.rejecteBidMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.rejected_bidrequests", id), me.MetricsRegistry) + am.dynamicFetchFailureMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.floors_account_fetch_err", id), me.MetricsRegistry) + am.floorsRequestMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.bidfloor_requests", id), me.MetricsRegistry) am.debugRequestMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.debug_requests", id), me.MetricsRegistry) am.bidsReceivedMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.bids_received", id), me.MetricsRegistry) am.priceHistogram = metrics.GetOrRegisterHistogram(fmt.Sprintf("account.%s.prices", id), me.MetricsRegistry, metrics.NewExpDecaySample(1028, 0.015)) @@ -619,6 +658,24 @@ func (me *Metrics) RecordStoredResponse(pubId string) { } } +func (me *Metrics) RecordRejectedBidsForAccount(pubId string) { + if pubId != PublisherUnknown { + me.getAccountMetrics(pubId).rejecteBidMeter.Mark(1) + } +} + +// RecordDynamicFetchFailure implements a part of the MetricsEngine interface. Records dynamic fetch failure +func (me *Metrics) RecordDynamicFetchFailure(pubId, code string) { + if pubId != PublisherUnknown { + me.getAccountMetrics(pubId).dynamicFetchFailureMeter.Mark(1) + } +} +func (me *Metrics) RecordFloorsRequestForAccount(pubId string) { + if pubId != PublisherUnknown { + me.getAccountMetrics(pubId).floorsRequestMeter.Mark(1) + } +} + func (me *Metrics) RecordImps(labels ImpLabels) { me.ImpMeter.Mark(int64(1)) if labels.BannerImps { @@ -748,8 +805,18 @@ func (me *Metrics) RecordDNSTime(dnsLookupTime time.Duration) { me.DNSLookupTimer.Update(dnsLookupTime) } -func (me *Metrics) RecordTLSHandshakeTime(tlsHandshakeTime time.Duration) { - me.TLSHandshakeTimer.Update(tlsHandshakeTime) +func (me *Metrics) RecordTLSHandshakeTime(adapterName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) { + if me.MetricsDisabled.AdapterConnectionMetrics { + return + } + + am, ok := me.AdapterMetrics[string(adapterName)] + if !ok { + glog.Errorf("Trying to log adapter TLS Handshake metrics for %s: adapter not found", string(adapterName)) + return + } + + am.TLSHandshakeTimer.Update(tlsHandshakeTime) } func (me *Metrics) RecordBidderServerResponseTime(bidderServerResponseTime time.Duration) { @@ -802,6 +869,13 @@ func (me *Metrics) RecordAdapterPrice(labels AdapterLabels, cpm float64) { } } +// RecordRejectedBidsForBidder implements a part of the MetricsEngine interface. Records rejected bids from bidder +func (me *Metrics) RecordRejectedBidsForBidder(bidder openrtb_ext.BidderName) { + if keyMeter, exists := me.FloorRejectedBidsMeter[bidder]; exists { + keyMeter.Mark(1) + } +} + // RecordAdapterTime implements a part of the MetricsEngine interface. Records the adapter response time func (me *Metrics) RecordAdapterTime(labels AdapterLabels, length time.Duration) { adapterStr := string(labels.Adapter) diff --git a/metrics/go_metrics_ow.go b/metrics/go_metrics_ow.go new file mode 100644 index 00000000000..e03a402ee7a --- /dev/null +++ b/metrics/go_metrics_ow.go @@ -0,0 +1,46 @@ +package metrics + +import "time" + +// RecordAdapterDuplicateBidID as noop +func (me *Metrics) RecordAdapterDuplicateBidID(adaptor string, collisions int) { +} + +// RecordRequestHavingDuplicateBidID as noop +func (me *Metrics) RecordRequestHavingDuplicateBidID() { +} + +// RecordPodImpGenTime as a noop +func (me *Metrics) RecordPodImpGenTime(labels PodLabels, startTime time.Time) { +} + +// RecordPodCombGenTime as a noop +func (me *Metrics) RecordPodCombGenTime(labels PodLabels, elapsedTime time.Duration) { +} + +// RecordPodCompititveExclusionTime as a noop +func (me *Metrics) RecordPodCompititveExclusionTime(labels PodLabels, elapsedTime time.Duration) { +} + +// RecordAdapterVideoBidDuration as a noop +func (me *Metrics) RecordAdapterVideoBidDuration(labels AdapterLabels, videoBidDuration int) { +} + +// RecordAdapterVideoBidDuration as a noop +func (me *Metrics) RecordRejectedBids(pubid, biddder, code string) { +} + +// RecordBids as a noop +func (me *Metrics) RecordBids(pubid, profileid, biddder, deal string) { +} + +// RecordVastVersion as a noop +func (me *Metrics) RecordVastVersion(biddder, vastVersion string) { +} + +func (me *Metrics) RecordHttpCounter() { +} + +// RecordVASTTagType as a noop +func (me *Metrics) RecordVASTTagType(biddder, vastTag string) { +} diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 05529220f16..acc485119c1 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -2,6 +2,7 @@ package metrics import ( "fmt" + "strings" "testing" "time" @@ -406,20 +407,40 @@ func TestRecordDNSTime(t *testing.T) { } func TestRecordTLSHandshakeTime(t *testing.T) { + type testIn struct { + adapterName openrtb_ext.BidderName + tLSHandshakeDuration time.Duration + adapterMetricsEnabled bool + } + + type testOut struct { + expectedDuration time.Duration + } + testCases := []struct { - description string - tLSHandshakeDuration time.Duration - expectedDuration time.Duration + description string + in testIn + out testOut }{ { - description: "Five second TLS handshake time", - tLSHandshakeDuration: time.Second * 5, - expectedDuration: time.Second * 5, + description: "Five second TLS handshake time", + in: testIn{ + tLSHandshakeDuration: time.Second * 5, + adapterMetricsEnabled: true, + }, + out: testOut{ + expectedDuration: time.Second * 5, + }, }, { - description: "Zero TLS handshake time", - tLSHandshakeDuration: time.Duration(0), - expectedDuration: time.Duration(0), + description: "Zero TLS handshake time", + in: testIn{ + tLSHandshakeDuration: time.Duration(0), + adapterMetricsEnabled: true, + }, + out: testOut{ + expectedDuration: time.Duration(0), + }, }, } adapter := "AnyName" @@ -427,9 +448,10 @@ func TestRecordTLSHandshakeTime(t *testing.T) { registry := metrics.NewRegistry() m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) - m.RecordTLSHandshakeTime(test.tLSHandshakeDuration) + m.RecordTLSHandshakeTime(openrtb_ext.BidderName(strings.ToLower(adapter)), test.in.tLSHandshakeDuration) + + assert.Equal(t, test.out.expectedDuration.Nanoseconds(), m.AdapterMetrics[strings.ToLower(adapter)].TLSHandshakeTimer.Sum(), test.description) - assert.Equal(t, test.expectedDuration.Nanoseconds(), m.TLSHandshakeTimer.Sum(), test.description) } } @@ -903,6 +925,25 @@ func TestRecordSyncerSet(t *testing.T) { assert.Equal(t, m.SyncerSetsMeter["foo"][SyncerSetUidCleared].Count(), int64(1)) } +func TestRecordRejectedBidsForBidders(t *testing.T) { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon, openrtb_ext.BidderPubmatic}, config.DisabledMetrics{}, nil, nil) + + m.RecordFloorsRequestForAccount("1234") + + m.RecordRejectedBidsForAccount("1234") + m.RecordRejectedBidsForBidder(openrtb_ext.BidderAppnexus) + m.RecordRejectedBidsForBidder(openrtb_ext.BidderAppnexus) + + m.RecordRejectedBidsForBidder(openrtb_ext.BidderRubicon) + + assert.Equal(t, m.accountMetrics["1234"].floorsRequestMeter.Count(), int64(1)) + assert.Equal(t, m.accountMetrics["1234"].rejecteBidMeter.Count(), int64(1)) + assert.Equal(t, m.FloorRejectedBidsMeter[openrtb_ext.BidderAppnexus].Count(), int64(2)) + assert.Equal(t, m.FloorRejectedBidsMeter[openrtb_ext.BidderRubicon].Count(), int64(1)) + assert.Equal(t, m.FloorRejectedBidsMeter[openrtb_ext.BidderPubmatic].Count(), int64(0)) +} + func TestStoredResponses(t *testing.T) { testCases := []struct { description string diff --git a/metrics/metrics.go b/metrics/metrics.go index 7d3dc819341..682589f8e88 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -54,6 +54,15 @@ type ImpLabels struct { NativeImps bool } +// PodLabels defines metric labels describing algorithm type +// and other labels as per scenario +type PodLabels struct { + AlgorithmName string // AlgorithmName which is used for generating impressions + NoOfImpressions *int // NoOfImpressions represents number of impressions generated + NoOfCombinations *int // NoOfCombinations represents number of combinations generated + NoOfResponseBids *int // NoOfResponseBids represents number of bids responded (including bids with similar duration) +} + // RequestLabels defines metric labels describing the result of a network request. type RequestLabels struct { RequestStatus RequestStatus @@ -426,6 +435,7 @@ func SyncerSetUidStatuses() []SyncerSetUidStatus { // two groups should be consistent within themselves, but comparing numbers between groups // is generally not useful. type MetricsEngine interface { + OWMetricsEngine RecordConnectionAccept(success bool) RecordTMaxTimeout() RecordConnectionClose(success bool) @@ -436,7 +446,7 @@ type MetricsEngine interface { RecordAdapterRequest(labels AdapterLabels) RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) RecordDNSTime(dnsLookupTime time.Duration) - RecordTLSHandshakeTime(tlsHandshakeTime time.Duration) + RecordTLSHandshakeTime(adapterName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) RecordBidderServerResponseTime(bidderServerResponseTime time.Duration) RecordAdapterPanic(labels AdapterLabels) RecordAdapterBidReceived(labels AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) @@ -446,6 +456,9 @@ type MetricsEngine interface { RecordSyncerRequest(key string, status SyncerCookieSyncStatus) RecordSetUid(status SetUidStatus) RecordSyncerSet(key string, status SyncerSetUidStatus) + RecordRejectedBidsForBidder(bidder openrtb_ext.BidderName) + RecordRejectedBidsForAccount(pubId string) + RecordFloorsRequestForAccount(pubId string) RecordStoredReqCacheResult(cacheResult CacheResult, inc int) RecordStoredImpCacheResult(cacheResult CacheResult, inc int) RecordAccountCacheResult(cacheResult CacheResult, inc int) @@ -471,4 +484,44 @@ type MetricsEngine interface { RecordModuleSuccessRejected(labels ModuleLabels) RecordModuleExecutionError(labels ModuleLabels) RecordModuleTimeout(labels ModuleLabels) + + // RecordAdapterDuplicateBidID captures the bid.ID collisions when adaptor + // gives the bid response with multiple bids containing same bid.ID + RecordAdapterDuplicateBidID(adaptor string, collisions int) + + // RecordRequestHavingDuplicateBidID keeps track off how many request got bid.id collision + // detected + RecordRequestHavingDuplicateBidID() + + // ad pod specific metrics + + // RecordPodImpGenTime records number of impressions generated and time taken + // by underneath algorithm to generate them + // labels accept name of the algorithm and no of impressions generated + // startTime indicates the time at which algorithm started + // This function will take care of computing the elpased time + RecordPodImpGenTime(labels PodLabels, startTime time.Time) + + // RecordPodCombGenTime records number of combinations generated and time taken + // by underneath algorithm to generate them + // labels accept name of the algorithm and no of combinations generated + // elapsedTime indicates the time taken by combination generator to compute all requested combinations + // This function will take care of computing the elpased time + RecordPodCombGenTime(labels PodLabels, elapsedTime time.Duration) + + // RecordPodCompititveExclusionTime records time take by competitive exclusion + // to compute the final Ad pod Response. + // labels accept name of the algorithm and no of combinations evaluated, total bids + // elapsedTime indicates the time taken by competitive exclusion to form final ad pod response using combinations and exclusion algorithm + // This function will take care of computing the elpased time + RecordPodCompititveExclusionTime(labels PodLabels, elapsedTime time.Duration) + + //RecordAdapterVideoBidDuration records actual ad duration returned by the bidder + RecordAdapterVideoBidDuration(labels AdapterLabels, videoBidDuration int) + + //RecordDynamicFetchFailure records the dynamic fetch failure labeled by pubid and reason code + RecordDynamicFetchFailure(pubId, code string) + + //RecordRejectedBids records the rejected bids labeled by pubid, bidder and reason code + RecordRejectedBids(pubid, bidder, code string) } diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index ef9fdcaf7a4..07e09db5f0a 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -72,8 +72,8 @@ func (me *MetricsEngineMock) RecordDNSTime(dnsLookupTime time.Duration) { me.Called(dnsLookupTime) } -func (me *MetricsEngineMock) RecordTLSHandshakeTime(tlsHandshakeTime time.Duration) { - me.Called(tlsHandshakeTime) +func (me *MetricsEngineMock) RecordTLSHandshakeTime(bidderName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) { + me.Called(bidderName, tlsHandshakeTime) } // RecordBidderServerResponseTime mock @@ -121,6 +121,10 @@ func (me *MetricsEngineMock) RecordSyncerSet(key string, status SyncerSetUidStat me.Called(key, status) } +func (me *MetricsEngineMock) RecordRejectedBidsForBidder(bidder openrtb_ext.BidderName) { + me.Called(bidder) +} + // RecordStoredReqCacheResult mock func (me *MetricsEngineMock) RecordStoredReqCacheResult(cacheResult CacheResult, inc int) { me.Called(cacheResult, inc) @@ -170,6 +174,14 @@ func (me *MetricsEngineMock) RecordStoredResponse(pubId string) { me.Called(pubId) } +func (me *MetricsEngineMock) RecordRejectedBidsForAccount(pubId string) { + me.Called(pubId) +} + +func (me *MetricsEngineMock) RecordFloorsRequestForAccount(pubId string) { + me.Called(pubId) +} + func (me *MetricsEngineMock) RecordAdsCertReq(success bool) { me.Called(success) } @@ -221,3 +233,14 @@ func (me *MetricsEngineMock) RecordModuleExecutionError(labels ModuleLabels) { func (me *MetricsEngineMock) RecordModuleTimeout(labels ModuleLabels) { me.Called(labels) } + +func (me *MetricsEngineMock) RecordRejectedBids(pubid, bidder, code string) { + me.Called(pubid, bidder, code) +} + +func (me *MetricsEngineMock) RecordDynamicFetchFailure(pubId, code string) { + me.Called(pubId, code) +} + +func (me *MetricsEngineMock) RecordHttpCounter() { +} diff --git a/metrics/metrics_mock_ow.go b/metrics/metrics_mock_ow.go new file mode 100644 index 00000000000..a3b06ddd9c8 --- /dev/null +++ b/metrics/metrics_mock_ow.go @@ -0,0 +1,47 @@ +package metrics + +import "time" + +// RecordAdapterDuplicateBidID mock +func (me *MetricsEngineMock) RecordAdapterDuplicateBidID(adaptor string, collisions int) { + me.Called(adaptor, collisions) +} + +// RecordRequestHavingDuplicateBidID mock +func (me *MetricsEngineMock) RecordRequestHavingDuplicateBidID() { + me.Called() +} + +// RecordPodImpGenTime mock +func (me *MetricsEngineMock) RecordPodImpGenTime(labels PodLabels, startTime time.Time) { + me.Called(labels, startTime) +} + +// RecordPodCombGenTime mock +func (me *MetricsEngineMock) RecordPodCombGenTime(labels PodLabels, elapsedTime time.Duration) { + me.Called(labels, elapsedTime) +} + +// RecordPodCompititveExclusionTime mock +func (me *MetricsEngineMock) RecordPodCompititveExclusionTime(labels PodLabels, elapsedTime time.Duration) { + me.Called(labels, elapsedTime) +} + +// RecordAdapterVideoBidDuration mock +func (me *MetricsEngineMock) RecordAdapterVideoBidDuration(labels AdapterLabels, videoBidDuration int) { + me.Called(labels, videoBidDuration) +} + +func (me *MetricsEngineMock) RecordBids(pubid, profileid, biddder, deal string) { + me.Called(pubid, profileid, biddder, deal) +} + +// RecordVastVersion mock +func (me *MetricsEngineMock) RecordVastVersion(coreBidder, vastVersion string) { + me.Called(coreBidder, vastVersion) +} + +// RecordVASTTagType mock +func (me *MetricsEngineMock) RecordVASTTagType(bidder, vastTagType string) { + me.Called(bidder, vastTagType) +} diff --git a/metrics/metrics_ow.go b/metrics/metrics_ow.go new file mode 100644 index 00000000000..958394dd03d --- /dev/null +++ b/metrics/metrics_ow.go @@ -0,0 +1,11 @@ +package metrics + +type OWMetricsEngine interface { + RecordHttpCounter() + //RecordBids records the bidder deal bids labeled by pubid, profile, bidder and deal + RecordBids(pubid, profileid, bidder, deal string) + //RecordVastVersion record the count of vast version labelled by bidder and vast version + RecordVastVersion(coreBidder, vastVersion string) + //RecordVASTTagType record the count of vast tag type labeled by bidder and vast tag + RecordVASTTagType(bidder, vastTagType string) +} diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 6bd30544662..4dd0e133408 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -15,6 +15,7 @@ import ( // Metrics defines the Prometheus metrics backing the MetricsEngine implementation. type Metrics struct { + OWMetrics Registerer prometheus.Registerer Gatherer *prometheus.Registry @@ -47,7 +48,6 @@ type Metrics struct { storedVideoErrors *prometheus.CounterVec timeoutNotifications *prometheus.CounterVec dnsLookupTimer prometheus.Histogram - tlsHandhakeTimer prometheus.Histogram privacyCCPA *prometheus.CounterVec privacyCOPPA *prometheus.CounterVec privacyLMT *prometheus.CounterVec @@ -76,6 +76,8 @@ type Metrics struct { adapterBidResponseSecureMarkupError *prometheus.CounterVec adapterBidResponseSecureMarkupWarn *prometheus.CounterVec + tlsHandhakeTimer *prometheus.HistogramVec + // Syncer Metrics syncerRequests *prometheus.CounterVec syncerSets *prometheus.CounterVec @@ -98,6 +100,7 @@ type Metrics struct { moduleSuccessRejects map[string]*prometheus.CounterVec moduleExecutionErrors map[string]*prometheus.CounterVec moduleTimeouts map[string]*prometheus.CounterVec + // Ad Pod Metrics metricsDisabled config.DisabledMetrics } @@ -149,6 +152,14 @@ const ( requestFailed = "failed" ) +// pod specific constants +const ( + podAlgorithm = "algorithm" + podNoOfImpressions = "no_of_impressions" + podTotalCombinations = "total_combinations" + podNoOfResponseBids = "no_of_response_bids" +) + const ( sourceLabel = "source" sourceRequest = "request" @@ -160,7 +171,7 @@ const ( ) // NewMetrics initializes a new Prometheus metrics instance with preloaded label values. -func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMetrics, syncerKeys []string, moduleStageNames map[string][]string) *Metrics { +func NewMetrics(cfg config.PrometheusMetrics, reg *prometheus.Registry, disabledMetrics config.DisabledMetrics, syncerKeys []string, moduleStageNames map[string][]string) *Metrics { standardTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1} cacheWriteTimeBuckets := []float64{0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1} priceBuckets := []float64{250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000} @@ -168,7 +179,10 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet overheadTimeBuckets := []float64{0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1} metrics := Metrics{} - reg := prometheus.NewRegistry() + if reg == nil { + reg = prometheus.NewRegistry() + } + metrics.init(cfg, reg) metrics.metricsDisabled = disabledMetrics metrics.connectionsClosed = newCounterWithoutLabels(cfg, reg, @@ -309,10 +323,10 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "Seconds to resolve DNS", standardTimeBuckets) - metrics.tlsHandhakeTimer = newHistogram(cfg, reg, - "tls_handshake_time", - "Seconds to perform TLS Handshake", - standardTimeBuckets) + // metrics.tlsHandhakeTimer = newHistogram(cfg, reg, + // "tls_handshake_time", + // "Seconds to perform TLS Handshake", + // standardTimeBuckets) metrics.privacyCCPA = newCounter(cfg, reg, "privacy_ccpa", @@ -398,6 +412,12 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "Seconds from when the connection was requested until it is either created or reused", []string{adapterLabel}, standardTimeBuckets) + + metrics.tlsHandhakeTimer = newHistogramVec(cfg, reg, + "tls_handshake_time", + "Seconds to perform TLS Handshake", + []string{adapterLabel}, + standardTimeBuckets) } metrics.adapterBidResponseValidationSizeError = newCounter(cfg, reg, @@ -512,9 +532,7 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet metrics.Registerer = prometheus.WrapRegistererWithPrefix(metricsPrefix, reg) metrics.Registerer.MustRegister(promCollector.NewGoCollector()) - preloadLabelValues(&metrics, syncerKeys, moduleStageNames) - return &metrics } @@ -571,6 +589,7 @@ func createModulesMetrics(cfg config.PrometheusMetrics, registry *prometheus.Reg fmt.Sprintf("modules_%s_timeouts", module), "Count of module timeouts labeled by stage name.", []string{stageLabel}) + } } @@ -778,6 +797,14 @@ func (m *Metrics) RecordAdapterRequest(labels metrics.AdapterLabels) { } } +func (m *Metrics) RecordRejectedBidsForBidder(Adapter openrtb_ext.BidderName) { + if m.rejectedBids != nil { + m.rejectedBids.With(prometheus.Labels{ + adapterLabel: string(Adapter), + }).Inc() + } +} + // Keeps track of created and reused connections to adapter bidders and the time from the // connection request, to the connection creation, or reuse from the pool across all engines func (m *Metrics) RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) { @@ -805,8 +832,11 @@ func (m *Metrics) RecordDNSTime(dnsLookupTime time.Duration) { m.dnsLookupTimer.Observe(dnsLookupTime.Seconds()) } -func (m *Metrics) RecordTLSHandshakeTime(tlsHandshakeTime time.Duration) { - m.tlsHandhakeTimer.Observe(tlsHandshakeTime.Seconds()) +func (m *Metrics) RecordTLSHandshakeTime(adapterName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) { + // m.tlsHandhakeTimer.Observe(tlsHandshakeTime.Seconds()) + m.tlsHandhakeTimer.With(prometheus.Labels{ + adapterLabel: string(adapterName), + }).Observe(tlsHandshakeTime.Seconds()) } func (m *Metrics) RecordBidderServerResponseTime(bidderServerResponseTime time.Duration) { diff --git a/metrics/prometheus/prometheus_ow.go b/metrics/prometheus/prometheus_ow.go new file mode 100644 index 00000000000..789788a93ce --- /dev/null +++ b/metrics/prometheus/prometheus_ow.go @@ -0,0 +1,260 @@ +package prometheusmetrics + +import ( + "strconv" + "time" + + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prometheus/client_golang/prometheus" +) + +const ( + pubIDLabel = "pubid" + bidderLabel = "bidder" + codeLabel = "code" + profileLabel = "profileid" + dealLabel = "deal" + vastTagTypeLabel = "type" +) + +type OWMetrics struct { + vastTagType *prometheus.CounterVec + // Rejected Bids + rejectedBids *prometheus.CounterVec + bids *prometheus.CounterVec + vastVersion *prometheus.CounterVec + //rejectedBids *prometheus.CounterVec + accountRejectedBid *prometheus.CounterVec + accountFloorsRequest *prometheus.CounterVec + + //Dynamic Fetch Failure + dynamicFetchFailure *prometheus.CounterVec + adapterDuplicateBidIDCounter *prometheus.CounterVec + requestsDuplicateBidIDCounter prometheus.Counter // total request having duplicate bid.id for given bidder + adapterVideoBidDuration *prometheus.HistogramVec + + // podImpGenTimer indicates time taken by impression generator + // algorithm to generate impressions for given ad pod request + podImpGenTimer *prometheus.HistogramVec + + // podImpGenTimer indicates time taken by combination generator + // algorithm to generate combination based on bid response and ad pod request + podCombGenTimer *prometheus.HistogramVec + + // podCompExclTimer indicates time taken by compititve exclusion + // algorithm to generate final pod response based on bid response and ad pod request + podCompExclTimer *prometheus.HistogramVec + httpCounter prometheus.Counter +} + +func newHttpCounter(cfg config.PrometheusMetrics, registry *prometheus.Registry) prometheus.Counter { + httpCounter := prometheus.NewCounter(prometheus.CounterOpts{ + Name: "http_requests_total", + Help: "Number of http requests.", + }) + registry.MustRegister(httpCounter) + return httpCounter +} + +// RecordAdapterDuplicateBidID captures the bid.ID collisions when adaptor +// gives the bid response with multiple bids containing same bid.ID +// ensure collisions value is greater than 1. This function will not give any error +// if collisions = 1 is passed +func (m *OWMetrics) RecordAdapterDuplicateBidID(adaptor string, collisions int) { + m.adapterDuplicateBidIDCounter.With(prometheus.Labels{ + adapterLabel: adaptor, + }).Add(float64(collisions)) +} + +// RecordRequestHavingDuplicateBidID keeps count of request when duplicate bid.id is +// detected in partner's response +func (m *OWMetrics) RecordRequestHavingDuplicateBidID() { + m.requestsDuplicateBidIDCounter.Inc() +} + +// pod specific metrics + +// recordAlgoTime is common method which handles algorithm time performance +func recordAlgoTime(timer *prometheus.HistogramVec, labels metrics.PodLabels, elapsedTime time.Duration) { + + pmLabels := prometheus.Labels{ + podAlgorithm: labels.AlgorithmName, + } + + if labels.NoOfImpressions != nil { + pmLabels[podNoOfImpressions] = strconv.Itoa(*labels.NoOfImpressions) + } + if labels.NoOfCombinations != nil { + pmLabels[podTotalCombinations] = strconv.Itoa(*labels.NoOfCombinations) + } + if labels.NoOfResponseBids != nil { + pmLabels[podNoOfResponseBids] = strconv.Itoa(*labels.NoOfResponseBids) + } + + timer.With(pmLabels).Observe(elapsedTime.Seconds()) +} + +// RecordPodImpGenTime records number of impressions generated and time taken +// by underneath algorithm to generate them +func (m *OWMetrics) RecordPodImpGenTime(labels metrics.PodLabels, start time.Time) { + elapsedTime := time.Since(start) + recordAlgoTime(m.podImpGenTimer, labels, elapsedTime) +} + +// RecordPodCombGenTime records number of combinations generated and time taken +// by underneath algorithm to generate them +func (m *OWMetrics) RecordPodCombGenTime(labels metrics.PodLabels, elapsedTime time.Duration) { + recordAlgoTime(m.podCombGenTimer, labels, elapsedTime) +} + +// RecordPodCompititveExclusionTime records number of combinations comsumed for forming +// final ad pod response and time taken by underneath algorithm to generate them +func (m *OWMetrics) RecordPodCompititveExclusionTime(labels metrics.PodLabels, elapsedTime time.Duration) { + recordAlgoTime(m.podCompExclTimer, labels, elapsedTime) +} + +// RecordAdapterVideoBidDuration records actual ad duration (>0) returned by the bidder +func (m *OWMetrics) RecordAdapterVideoBidDuration(labels metrics.AdapterLabels, videoBidDuration int) { + if videoBidDuration > 0 { + m.adapterVideoBidDuration.With(prometheus.Labels{adapterLabel: string(labels.Adapter)}).Observe(float64(videoBidDuration)) + } +} + +// RecordRejectedBids records rejected bids labeled by pubid, bidder and reason code +func (m *OWMetrics) RecordRejectedBids(pubid, biddder, code string) { + m.rejectedBids.With(prometheus.Labels{ + pubIDLabel: pubid, + bidderLabel: biddder, + codeLabel: code, + }).Inc() +} + +// RecordBids records bids labeled by pubid, profileid, bidder and deal +func (m *OWMetrics) RecordBids(pubid, profileid, biddder, deal string) { + m.bids.With(prometheus.Labels{ + pubIDLabel: pubid, + profileLabel: profileid, + bidderLabel: biddder, + dealLabel: deal, + }).Inc() +} + +// RecordVastVersion record the count of vast version labelled by bidder and vast version +func (m *OWMetrics) RecordVastVersion(coreBiddder, vastVersion string) { + m.vastVersion.With(prometheus.Labels{ + adapterLabel: coreBiddder, + versionLabel: vastVersion, + }).Inc() +} + +// RecordVASTTagType record the count of vast tags labeled by bidder and vast tag +func (m *OWMetrics) RecordVASTTagType(bidder, vastTagType string) { + m.vastTagType.With(prometheus.Labels{ + bidderLabel: bidder, + vastTagTypeLabel: vastTagType, + }).Inc() +} +func (m *Metrics) RecordRejectedBidsForAccount(pubId string) { + if pubId != metrics.PublisherUnknown { + m.accountRejectedBid.With(prometheus.Labels{ + accountLabel: pubId, + }).Inc() + } +} + +func (m *Metrics) RecordFloorsRequestForAccount(pubId string) { + if pubId != metrics.PublisherUnknown { + m.accountFloorsRequest.With(prometheus.Labels{ + accountLabel: pubId, + }).Inc() + } +} +func (m *Metrics) RecordDynamicFetchFailure(pubId, code string) { + if pubId != metrics.PublisherUnknown { + m.dynamicFetchFailure.With(prometheus.Labels{ + accountLabel: pubId, + codeLabel: code, + }).Inc() + } +} + +func (m *Metrics) RecordHttpCounter() { + m.httpCounter.Inc() +} + +func (m *OWMetrics) init(cfg config.PrometheusMetrics, reg *prometheus.Registry) { + m.httpCounter = newHttpCounter(cfg, reg) + m.rejectedBids = newCounter(cfg, reg, + "rejected_bids", + "Count of rejected bids by publisher id, bidder and rejection reason code", + []string{pubIDLabel, bidderLabel, codeLabel}) + + m.vastVersion = newCounter(cfg, reg, + "vast_version", + "Count of vast version by bidder and vast version", + []string{adapterLabel, versionLabel}) + + m.vastTagType = newCounter(cfg, reg, + "vast_tag_type", + "Count of vast tag by bidder and vast tag type (Wrapper, InLine, URL, Unknown)", + []string{bidderLabel, vastTagTypeLabel}) + + m.dynamicFetchFailure = newCounter(cfg, reg, + "floors_account_fetch_err", + "Count of failures in case of dynamic fetch labeled by account", + []string{codeLabel, accountLabel}) + + m.adapterDuplicateBidIDCounter = newCounter(cfg, reg, + "duplicate_bid_ids", + "Number of collisions observed for given adaptor", + []string{adapterLabel}) + + m.requestsDuplicateBidIDCounter = newCounterWithoutLabels(cfg, reg, + "requests_having_duplicate_bid_ids", + "Count of number of request where bid collision is detected.") + + m.adapterVideoBidDuration = newHistogramVec(cfg, reg, + "adapter_vidbid_dur", + "Video Ad durations returned by the bidder", []string{adapterLabel}, + []float64{4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 120}) + + m.bids = newCounter(cfg, reg, + "bids", + "Count of no of bids by publisher id, profile, bidder and deal", + []string{pubIDLabel, profileLabel, bidderLabel, dealLabel}) + + m.accountRejectedBid = newCounter(cfg, reg, + "floors_account_rejected_bid_requests", + "Count of total requests to Prebid Server that have rejected bids due to floors enfocement labled by account", + []string{accountLabel}) + + m.accountFloorsRequest = newCounter(cfg, reg, + "floors_account_requests", + "Count of total requests to Prebid Server that have non-zero imp.bidfloor labled by account", + []string{accountLabel}) + + // adpod specific metrics + m.podImpGenTimer = newHistogramVec(cfg, reg, + "impr_gen", + "Time taken by Ad Pod Impression Generator in seconds", []string{podAlgorithm, podNoOfImpressions}, + // 200 µS, 250 µS, 275 µS, 300 µS + //[]float64{0.000200000, 0.000250000, 0.000275000, 0.000300000}) + // 100 µS, 200 µS, 300 µS, 400 µS, 500 µS, 600 µS, + []float64{0.000100000, 0.000200000, 0.000300000, 0.000400000, 0.000500000, 0.000600000}) + + m.podCombGenTimer = newHistogramVec(cfg, reg, + "comb_gen", + "Time taken by Ad Pod Combination Generator in seconds", []string{podAlgorithm, podTotalCombinations}, + // 200 µS, 250 µS, 275 µS, 300 µS + //[]float64{0.000200000, 0.000250000, 0.000275000, 0.000300000}) + []float64{0.000100000, 0.000200000, 0.000300000, 0.000400000, 0.000500000, 0.000600000}) + + m.podCompExclTimer = newHistogramVec(cfg, reg, + "comp_excl", + "Time taken by Ad Pod Compititve Exclusion in seconds", []string{podAlgorithm, podNoOfResponseBids}, + // 200 µS, 250 µS, 275 µS, 300 µS + //[]float64{0.000200000, 0.000250000, 0.000275000, 0.000300000}) + []float64{0.000100000, 0.000200000, 0.000300000, 0.000400000, 0.000500000, 0.000600000}) + +} diff --git a/metrics/prometheus/prometheus_ow_test.go b/metrics/prometheus/prometheus_ow_test.go new file mode 100644 index 00000000000..35a8c9d05b8 --- /dev/null +++ b/metrics/prometheus/prometheus_ow_test.go @@ -0,0 +1,170 @@ +package prometheusmetrics + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" +) + +func TestRecordRejectedBids(t *testing.T) { + type testIn struct { + pubid, bidder, code string + } + type testOut struct { + expCount int + } + testCases := []struct { + name string + in testIn + out testOut + }{ + { + name: "record rejected bids", + in: testIn{ + pubid: "1010", + bidder: "bidder", + code: "100", + }, + out: testOut{ + expCount: 1, + }, + }, + } + for _, test := range testCases { + pm := createMetricsForTesting() + pm.RecordRejectedBids(test.in.pubid, test.in.bidder, test.in.code) + + assertCounterVecValue(t, + "", + "rejected_bids", + pm.rejectedBids, + float64(test.out.expCount), + prometheus.Labels{ + pubIDLabel: test.in.pubid, + bidderLabel: test.in.bidder, + codeLabel: test.in.code, + }) + } +} + +func TestRecordBids(t *testing.T) { + type testIn struct { + pubid, profileid, bidder, deal string + } + type testOut struct { + expCount int + } + testCases := []struct { + name string + in testIn + out testOut + }{ + { + name: "record bids", + in: testIn{ + pubid: "1010", + bidder: "bidder", + profileid: "11", + deal: "pubdeal", + }, + out: testOut{ + expCount: 1, + }, + }, + } + for _, test := range testCases { + pm := createMetricsForTesting() + pm.RecordBids(test.in.pubid, test.in.profileid, test.in.bidder, test.in.deal) + + assertCounterVecValue(t, + "", + "bids", + pm.bids, + float64(test.out.expCount), + prometheus.Labels{ + pubIDLabel: test.in.pubid, + bidderLabel: test.in.bidder, + profileLabel: test.in.profileid, + dealLabel: test.in.deal, + }) + } +} + +func TestRecordVastVersion(t *testing.T) { + type testIn struct { + coreBidder, vastVersion string + } + type testOut struct { + expCount int + } + testCases := []struct { + name string + in testIn + out testOut + }{ + { + name: "record vast version", + in: testIn{ + coreBidder: "bidder", + vastVersion: "2.0", + }, + out: testOut{ + expCount: 1, + }, + }, + } + for _, test := range testCases { + pm := createMetricsForTesting() + pm.RecordVastVersion(test.in.coreBidder, test.in.vastVersion) + assertCounterVecValue(t, + "", + "record vastVersion", + pm.vastVersion, + float64(test.out.expCount), + prometheus.Labels{ + adapterLabel: test.in.coreBidder, + versionLabel: test.in.vastVersion, + }) + } +} + +func TestRecordVASTTagType(t *testing.T) { + type args struct { + bidder, vastTagType string + } + type want struct { + expCount int + } + tests := []struct { + name string + args args + want want + }{ + { + name: "record_vast_tag", + args: args{ + bidder: "bidder", + vastTagType: "Wrapper", + }, + want: want{ + expCount: 1, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + pm := createMetricsForTesting() + pm.RecordVASTTagType(tt.args.bidder, tt.args.vastTagType) + assertCounterVecValue(t, + "", + "record vastTag", + pm.vastTagType, + float64(tt.want.expCount), + prometheus.Labels{ + bidderLabel: tt.args.bidder, + vastTagTypeLabel: tt.args.vastTagType, + }) + }) + } +} diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index a74c8b6c0fa..2a96832ecd6 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -2,6 +2,7 @@ package prometheusmetrics import ( "fmt" + "strconv" "testing" "time" @@ -21,7 +22,7 @@ func createMetricsForTesting() *Metrics { Port: 8080, Namespace: "prebid", Subsystem: "server", - }, config.DisabledMetrics{}, syncerKeys, modulesStages) + }, nil, config.DisabledMetrics{}, syncerKeys, modulesStages) } func TestMetricCountGatekeeping(t *testing.T) { @@ -1451,35 +1452,57 @@ func TestRecordDNSTime(t *testing.T) { } func TestRecordTLSHandshakeTime(t *testing.T) { - testCases := []struct { - description string + type testIn struct { + adapterName openrtb_ext.BidderName tLSHandshakeDuration time.Duration - expectedDuration float64 - expectedCount uint64 + } + + type testOut struct { + expectedDuration float64 + expectedCount uint64 + } + + testCases := []struct { + description string + in testIn + out testOut }{ { - description: "Five second DNS lookup time", - tLSHandshakeDuration: time.Second * 5, - expectedDuration: 5, - expectedCount: 1, + description: "Five second DNS lookup time", + in: testIn{ + adapterName: openrtb_ext.BidderAppnexus, + tLSHandshakeDuration: time.Second * 5, + }, + out: testOut{ + expectedDuration: 5, + expectedCount: 1, + }, }, { - description: "Zero DNS lookup time", - tLSHandshakeDuration: 0, - expectedDuration: 0, - expectedCount: 1, + description: "Zero DNS lookup time", + in: testIn{ + adapterName: openrtb_ext.BidderAppnexus, + tLSHandshakeDuration: 0, + }, + out: testOut{ + expectedDuration: 0, + expectedCount: 1, + }, }, } for i, test := range testCases { pm := createMetricsForTesting() - pm.RecordTLSHandshakeTime(test.tLSHandshakeDuration) + assertDesciptions := []string{ + fmt.Sprintf("[%d] Incorrect number of histogram entries. Desc: %s", i+1, test.description), + fmt.Sprintf("[%d] Incorrect number of histogram cumulative values. Desc: %s", i+1, test.description), + } - m := dto.Metric{} - pm.tlsHandhakeTimer.Write(&m) - histogram := *m.GetHistogram() + pm.RecordTLSHandshakeTime(test.in.adapterName, test.in.tLSHandshakeDuration) - assert.Equal(t, test.expectedCount, histogram.GetSampleCount(), "[%d] Incorrect number of histogram entries. Desc: %s\n", i, test.description) - assert.Equal(t, test.expectedDuration, histogram.GetSampleSum(), "[%d] Incorrect number of histogram cumulative values. Desc: %s\n", i, test.description) + // Assert TLS Handshake time + histogram := getHistogramFromHistogramVec(pm.tlsHandhakeTimer, adapterLabel, string(test.in.adapterName)) + assert.Equal(t, test.out.expectedCount, histogram.GetSampleCount(), assertDesciptions[0]) + assert.Equal(t, test.out.expectedDuration, histogram.GetSampleSum(), assertDesciptions[1]) } } @@ -1634,16 +1657,20 @@ func TestDisabledMetrics(t *testing.T) { Port: 8080, Namespace: "prebid", Subsystem: "server", - }, config.DisabledMetrics{ - AdapterConnectionMetrics: true, - AdapterGDPRRequestBlocked: true, }, - nil, nil) + prometheus.NewRegistry(), + config.DisabledMetrics{ + AdapterConnectionMetrics: true, + AdapterGDPRRequestBlocked: true, + }, + nil, + nil) // Assert counter vector was not initialized assert.Nil(t, prometheusMetrics.adapterReusedConnections, "Counter Vector adapterReusedConnections should be nil") assert.Nil(t, prometheusMetrics.adapterCreatedConnections, "Counter Vector adapterCreatedConnections should be nil") assert.Nil(t, prometheusMetrics.adapterConnectionWaitTime, "Counter Vector adapterConnectionWaitTime should be nil") + assert.Nil(t, prometheusMetrics.tlsHandhakeTimer, "Counter Vector tlsHandhakeTimer should be nil") assert.Nil(t, prometheusMetrics.adapterGDPRBlockedRequests, "Counter Vector adapterGDPRBlockedRequests should be nil") } @@ -1725,6 +1752,187 @@ func TestRecordRequestPrivacy(t *testing.T) { }) } +// TestRecordRequestDuplicateBidID checks RecordRequestDuplicateBidID +func TestRecordRequestDuplicateBidID(t *testing.T) { + m := createMetricsForTesting() + m.RecordRequestHavingDuplicateBidID() + // verify total no of requests which detected collision + assertCounterValue(t, "request cnt having duplicate bid.id", "request cnt having duplicate bid.id", m.requestsDuplicateBidIDCounter, float64(1)) +} + +// TestRecordAdapterDuplicateBidID checks RecordAdapterDuplicateBidID +func TestRecordAdapterDuplicateBidID(t *testing.T) { + type collisions struct { + simulate int // no of bids to be simulate with same bid.id + expect int // no of collisions expected to be recorded by metrics engine for given bidder + } + type bidderCollisions = map[string]collisions + testCases := []struct { + scenario string + bidderCollisions bidderCollisions // represents no of collisions detected for bid.id at bidder level for given request + expectCollisions int + }{ + {scenario: "invalid collision value", bidderCollisions: map[string]collisions{"bidder-1": {simulate: -1, expect: 0}}}, + {scenario: "no collision", bidderCollisions: map[string]collisions{"bidder-1": {simulate: 0, expect: 0}}}, + {scenario: "one collision", bidderCollisions: map[string]collisions{"bidder-1": {simulate: 1, expect: 1}}}, + {scenario: "multiple collisions", bidderCollisions: map[string]collisions{"bidder-1": {simulate: 2, expect: 2}}}, + {scenario: "multiple bidders", bidderCollisions: map[string]collisions{"bidder-1": {simulate: 2, expect: 2}, "bidder-2": {simulate: 4, expect: 4}}}, + {scenario: "multiple bidders with bidder-1 no collision", bidderCollisions: map[string]collisions{"bidder-1": {simulate: 0, expect: 0}, + "bidder-2": {simulate: 4, expect: 4}}}, + } + + for _, testcase := range testCases { + m := createMetricsForTesting() + for bidder, collisions := range testcase.bidderCollisions { + for collision := 1; collision <= collisions.simulate; collision++ { + m.RecordAdapterDuplicateBidID(bidder, 1) + } + assertCounterVecValue(t, testcase.scenario, testcase.scenario, m.adapterDuplicateBidIDCounter, float64(collisions.expect), prometheus.Labels{ + adapterLabel: bidder, + }) + } + } +} + +func TestRecordPodImpGenTime(t *testing.T) { + impressions := 4 + testAlgorithmMetrics(t, impressions, func(m *Metrics) dto.Histogram { + m.RecordPodImpGenTime(metrics.PodLabels{AlgorithmName: "sample_imp_algo", NoOfImpressions: &impressions}, time.Now()) + return getHistogramFromHistogramVec(m.podImpGenTimer, podNoOfImpressions, strconv.Itoa(impressions)) + }) +} + +func TestRecordPodCombGenTime(t *testing.T) { + combinations := 5 + testAlgorithmMetrics(t, combinations, func(m *Metrics) dto.Histogram { + m.RecordPodCombGenTime(metrics.PodLabels{AlgorithmName: "sample_comb_algo", NoOfCombinations: &combinations}, time.Since(time.Now())) + return getHistogramFromHistogramVec(m.podCombGenTimer, podTotalCombinations, strconv.Itoa(combinations)) + }) +} + +func TestRecordPodCompetitiveExclusionTime(t *testing.T) { + totalBids := 8 + testAlgorithmMetrics(t, totalBids, func(m *Metrics) dto.Histogram { + m.RecordPodCompititveExclusionTime(metrics.PodLabels{AlgorithmName: "sample_comt_excl_algo", NoOfResponseBids: &totalBids}, time.Since(time.Now())) + return getHistogramFromHistogramVec(m.podCompExclTimer, podNoOfResponseBids, strconv.Itoa(totalBids)) + }) +} + +func TestRecordAdapterVideoBidDuration(t *testing.T) { + + testCases := []struct { + description string + bidderAdDurations map[string][]int + expectedSum map[string]int + expectedCount map[string]int + expectedBuckets map[string]map[int]int // cumulative + }{ + { + description: "single bidder multiple ad durations", + bidderAdDurations: map[string][]int{ + "bidder_1": {5, 10, 11, 32}, + }, + expectedSum: map[string]int{"bidder_1": 58}, + expectedCount: map[string]int{"bidder_1": 4}, + expectedBuckets: map[string]map[int]int{ + "bidder_1": {5: 1, 10: 2, 15: 3, 35: 4}, // Upper bound : cumulative number + }, + }, + { + description: "multiple bidders multiple ad durations", + bidderAdDurations: map[string][]int{ + "bidder_1": {5, 10, 11, 32, 39}, + "bidder_2": {25, 30}, + }, + expectedSum: map[string]int{"bidder_1": 97, "bidder_2": 55}, + expectedCount: map[string]int{"bidder_1": 5, "bidder_2": 2}, + expectedBuckets: map[string]map[int]int{ + "bidder_1": {5: 1, 10: 2, 15: 3, 35: 4, 40: 5}, + "bidder_2": {25: 1, 30: 2}, + }, + }, + { + description: "bidder with 0 ad durations", + bidderAdDurations: map[string][]int{ + "bidder_1": {5, 0, 0, 27}, + }, + expectedSum: map[string]int{"bidder_1": 32}, + expectedCount: map[string]int{"bidder_1": 2}, // must exclude 2 observations having 0 durations + expectedBuckets: map[string]map[int]int{ + "bidder_1": {5: 1, 30: 2}, + }, + }, + { + description: "bidder with similar durations", + bidderAdDurations: map[string][]int{ + "bidder_1": {23, 23, 23}, + }, + expectedSum: map[string]int{"bidder_1": 69}, + expectedCount: map[string]int{"bidder_1": 3}, // + expectedBuckets: map[string]map[int]int{ + "bidder_1": {25: 3}, + }, + }, + { + description: "bidder with ad durations >= 60", + bidderAdDurations: map[string][]int{ + "bidder_1": {33, 60, 93, 90, 90, 120}, + }, + expectedSum: map[string]int{"bidder_1": 486}, + expectedCount: map[string]int{"bidder_1": 6}, // + expectedBuckets: map[string]map[int]int{ + "bidder_1": {35: 1, 60: 2, 120: 6}, + }, + }, + } + + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + m := createMetricsForTesting() + for adapterName, adDurations := range test.bidderAdDurations { + for _, adDuration := range adDurations { + m.RecordAdapterVideoBidDuration(metrics.AdapterLabels{ + Adapter: openrtb_ext.BidderName(adapterName), + }, adDuration) + } + result := getHistogramFromHistogramVec(m.adapterVideoBidDuration, adapterLabel, adapterName) + for bucketDuration, durationCnt := range test.expectedBuckets[adapterName] { + validBucket := false + for _, bucket := range result.GetBucket() { + if int(bucket.GetUpperBound()) == bucketDuration { + validBucket = true + assert.Equal(t, uint64(durationCnt), bucket.GetCumulativeCount()) + break + } + } + if !validBucket { + assert.Fail(t, "Invalid expected bucket = "+strconv.Itoa(bucketDuration)) + } + } + expectedCount := test.expectedCount[adapterName] + expectedSum := test.expectedSum[adapterName] + assertHistogram(t, "adapter_vidbid_dur", result, uint64(expectedCount), float64(expectedSum)) + } + }) + } +} + +func testAlgorithmMetrics(t *testing.T, input int, f func(m *Metrics) dto.Histogram) { + // test input + adRequests := 2 + m := createMetricsForTesting() + var result dto.Histogram + for req := 1; req <= adRequests; req++ { + result = f(m) + } + + // assert observations + assert.Equal(t, uint64(adRequests), result.GetSampleCount(), "ad requests : count") + for _, bucket := range result.Bucket { + assert.Equal(t, uint64(adRequests), bucket.GetCumulativeCount(), "total observations") + } +} + func assertCounterValue(t *testing.T, description, name string, counter prometheus.Counter, expected float64) { m := dto.Metric{} counter.Write(&m) @@ -1987,3 +2195,112 @@ func TestRecordModuleMetrics(t *testing.T) { } } } + +func TestRecordDynamicFetchFailure(t *testing.T) { + type testIn struct { + pubid, code string + } + type testOut struct { + expCount int + } + testCases := []struct { + description string + in testIn + out testOut + }{ + { + description: "record dynamic fetch failure", + in: testIn{ + pubid: "5890", + code: "1", + }, + out: testOut{ + expCount: 1, + }, + }, + } + for _, test := range testCases { + pm := createMetricsForTesting() + pm.RecordDynamicFetchFailure(test.in.pubid, test.in.code) + + assertCounterVecValue(t, + "", + "", + pm.dynamicFetchFailure, + float64(test.out.expCount), + prometheus.Labels{ + accountLabel: test.in.pubid, + codeLabel: test.in.code, + }) + } +} +func TestRecordFloorsRequestForAccount(t *testing.T) { + type testIn struct { + pubid string + } + type testOut struct { + expCount int + } + testCases := []struct { + description string + in testIn + out testOut + }{ + { + description: "record floors request", + in: testIn{ + pubid: "1010", + }, + out: testOut{ + expCount: 1, + }, + }, + } + for _, test := range testCases { + pm := createMetricsForTesting() + pm.RecordFloorsRequestForAccount(test.in.pubid) + assertCounterVecValue(t, + "", + "floors_account_requests", + pm.accountFloorsRequest, + float64(test.out.expCount), + prometheus.Labels{ + accountLabel: test.in.pubid, + }) + } +} +func TestRecordRejectedBidsForAccount(t *testing.T) { + type testIn struct { + pubid string + } + type testOut struct { + expCount int + } + testCases := []struct { + description string + in testIn + out testOut + }{ + { + description: "record rejected bid", + in: testIn{ + pubid: "1010", + }, + out: testOut{ + expCount: 1, + }, + }, + } + for _, test := range testCases { + pm := createMetricsForTesting() + pm.RecordRejectedBidsForAccount(test.in.pubid) + assertCounterVecValue(t, + "", + "floors_account_rejected_bid_requests", + pm.accountRejectedBid, + float64(test.out.expCount), + prometheus.Labels{ + accountLabel: test.in.pubid, + }) + } +} diff --git a/metrics/pubmatic_stats/stats.go b/metrics/pubmatic_stats/stats.go new file mode 100644 index 00000000000..c9a113178b5 --- /dev/null +++ b/metrics/pubmatic_stats/stats.go @@ -0,0 +1,11 @@ +package pubmaticstats + +// IncBidResponseByDealCountInPBS counts number of bids received from aliasBidder for +// publisher, profile +// if dealid is not present then value would be'nodeal' +var IncBidResponseByDealCountInPBS = func(publisher, profile, aliasBidder, dealId string) { +} + +// IncPartnerTimeoutInPBS counts partner timeouts fro given publisher, profile and aliasbidder +var IncPartnerTimeoutInPBS = func(publisher, profile, aliasBidder string) { +} diff --git a/metrics/pubmatic_stats/stats_test.go b/metrics/pubmatic_stats/stats_test.go new file mode 100644 index 00000000000..cb3fe918246 --- /dev/null +++ b/metrics/pubmatic_stats/stats_test.go @@ -0,0 +1,11 @@ +package pubmaticstats + +import "testing" + +func TestIncBidResponseByDealCountInPBS(t *testing.T) { + IncBidResponseByDealCountInPBS("some_publisher_id", "some_profile_id", "some_alias_bidder", "some_dealid") +} + +func TestIncPartnerTimeoutInPBS(t *testing.T) { + IncPartnerTimeoutInPBS("some_publisher_id", "some_profile_id", "some_alias_bidder") +} diff --git a/modules/builder.go b/modules/builder.go index e5d04e149af..c80567ecff5 100644 --- a/modules/builder.go +++ b/modules/builder.go @@ -2,6 +2,8 @@ package modules import ( prebidOrtb2blocking "github.com/prebid/prebid-server/v2/modules/prebid/ortb2blocking" + pubmaticOpenwrap "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap" + vastunwrap "github.com/prebid/prebid-server/v2/modules/pubmatic/vastunwrap" ) // builders returns mapping between module name and its builder @@ -11,5 +13,9 @@ func builders() ModuleBuilders { "prebid": { "ortb2blocking": prebidOrtb2blocking.Builder, }, + "pubmatic": { + "vastunwrap": vastunwrap.Builder, + "openwrap": pubmaticOpenwrap.Builder, + }, } } diff --git a/modules/helpers.go b/modules/helpers.go index 10890743691..8728f7c1e77 100644 --- a/modules/helpers.go +++ b/modules/helpers.go @@ -27,6 +27,12 @@ func createModuleStageNamesCollection(modules map[string]interface{}) (map[strin moduleStageNameCollector = addModuleStageName(moduleStageNameCollector, id, stageName) } + if _, ok := hook.(hookstage.BeforeValidationRequest); ok { + added = true + stageName := hooks.StageBeforeValidationRequest.String() + moduleStageNameCollector = addModuleStageName(moduleStageNameCollector, id, stageName) + } + if _, ok := hook.(hookstage.ProcessedAuctionRequest); ok { added = true stageName := hooks.StageProcessedAuctionRequest.String() diff --git a/modules/moduledeps/deps.go b/modules/moduledeps/deps.go index a1fa89173b4..ec582a66bf0 100644 --- a/modules/moduledeps/deps.go +++ b/modules/moduledeps/deps.go @@ -3,12 +3,16 @@ package moduledeps import ( "net/http" + "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/currency" + metricsCfg "github.com/prebid/prebid-server/v2/metrics/config" ) // ModuleDeps provides dependencies that custom modules may need for hooks execution. // Additional dependencies can be added here if modules need something more. type ModuleDeps struct { - HTTPClient *http.Client - RateConvertor *currency.RateConverter + HTTPClient *http.Client + RateConvertor *currency.RateConverter + MetricsCfg *config.Metrics + MetricsRegistry metricsCfg.MetricsRegistry } diff --git a/modules/prebid/ortb2blocking/README.md b/modules/prebid/ortb2blocking/README.md index a2176ca5c32..32bc5882617 100644 --- a/modules/prebid/ortb2blocking/README.md +++ b/modules/prebid/ortb2blocking/README.md @@ -14,5 +14,5 @@ This module allows Prebid Server host companies to better support adapters that Any suggestions or questions can be directed to [example@site.com]() e-mail. -Or just open new [issue](https://github.com/prebid/prebid-server/issues/new) -or [pull request](https://github.com/prebid/prebid-server/pulls) in this repository. +Or just open new [issue](https://github.com/prebid/prebid-server/v2/issues/new) +or [pull request](https://github.com/prebid/prebid-server/v2/pulls) in this repository. diff --git a/modules/pubmatic/openwrap/abtest.go b/modules/pubmatic/openwrap/abtest.go new file mode 100644 index 00000000000..f2a9f8a71ef --- /dev/null +++ b/modules/pubmatic/openwrap/abtest.go @@ -0,0 +1,119 @@ +package openwrap + +import ( + "strconv" + "strings" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +// CheckABTestEnabled checks whether a given request is AB test enabled or not +func CheckABTestEnabled(rctx models.RequestCtx) bool { + return models.GetVersionLevelPropertyFromPartnerConfig(rctx.PartnerConfigMap, models.AbTestEnabled) == "1" +} + +// ABTestProcessing function checks if test config should be applied and change the partner config accordingly +func ABTestProcessing(rctx models.RequestCtx) (map[int]map[string]string, bool) { + //test config logic + if CheckABTestEnabled(rctx) && ApplyTestConfig(rctx) { + return UpdateTestConfig(rctx), true + } + return nil, false +} + +// ApplyTestConfig checks if test config should be applied +func ApplyTestConfig(rctx models.RequestCtx) bool { + testGroupSize, err := strconv.Atoi(models.GetVersionLevelPropertyFromPartnerConfig(rctx.PartnerConfigMap, AppendTest(models.TestGroupSize))) + if err != nil || testGroupSize == 0 { + return false + } + + randomNumber := GetRandomNumberIn1To100() + return randomNumber <= testGroupSize +} + +// AppendTest appends "_test" string to given key +func AppendTest(key string) string { + return key + test +} + +// UpdateTestConfig returns the updated partnerconfig according to the test type +func UpdateTestConfig(rctx models.RequestCtx) map[int]map[string]string { + + //create copy of the map + newPartnerConfig := copyPartnerConfigMap(rctx.PartnerConfigMap) + + //read test type + testType := models.GetVersionLevelPropertyFromPartnerConfig(rctx.PartnerConfigMap, AppendTest(models.TestType)) + + //change partnerconfig based on test type + switch testType { + case models.TestTypeAuctionTimeout: + replaceControlConfig(newPartnerConfig, models.VersionLevelConfigID, models.SSTimeoutKey) + case models.TestTypePartners: + //check the partner config map for test partners + for partnerID, config := range rctx.PartnerConfigMap { + if partnerID == models.VersionLevelConfigID { + continue + } + + //if current partner is test enabled, update the config with test config + //otherwise if its a control partner, then remove it from final partner config map + if config[models.PartnerTestEnabledKey] == "1" { + for key := range config { + copyTestConfig(newPartnerConfig, partnerID, key) + } + + } else { + delete(newPartnerConfig, partnerID) + } + } + + case models.TestTypeClientVsServerPath: // TODO: can we deprecate this AB test type + for partnerID := range rctx.PartnerConfigMap { + if partnerID == models.VersionLevelConfigID { + continue + } + + //update the "serverSideEnabled" value with test config + replaceControlConfig(newPartnerConfig, partnerID, models.SERVER_SIDE_FLAG) + + } + default: + } + + return newPartnerConfig +} + +// copyPartnerConfigMap creates a copy of given partner config map +func copyPartnerConfigMap(m map[int]map[string]string) map[int]map[string]string { + cp := make(map[int]map[string]string) + for pid, conf := range m { + config := make(map[string]string) + for key, val := range conf { + config[key] = val + } + cp[pid] = config + } + return cp +} + +// replaceControlConfig replace control config with test config for a given key +func replaceControlConfig(partnerConfig map[int]map[string]string, partnerID int, key string) { + if testValue := partnerConfig[partnerID][AppendTest(key)]; testValue != "" { + partnerConfig[partnerID][key] = testValue + } + +} + +// copyTestConfig checks if the given key is test config, if yes it copies it in control config +func copyTestConfig(partnerConfig map[int]map[string]string, partnerID int, key string) { + //if the current key is test config + if strings.HasSuffix(key, test) { + if testValue := partnerConfig[partnerID][key]; testValue != "" { + //get control key for the given test key to copy data + controlKey := strings.TrimSuffix(key, test) + partnerConfig[partnerID][controlKey] = testValue + } + } +} diff --git a/modules/pubmatic/openwrap/abtest_test.go b/modules/pubmatic/openwrap/abtest_test.go new file mode 100644 index 00000000000..585a5c2ff4d --- /dev/null +++ b/modules/pubmatic/openwrap/abtest_test.go @@ -0,0 +1,606 @@ +package openwrap + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/stretchr/testify/assert" +) + +func TestCheckABTestEnabled(t *testing.T) { + type args struct { + rctx models.RequestCtx + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "AbTest_enabled_in_partner_config_abTestEnabled=1", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AbTestEnabled: "1", + }, + }, + }, + }, + want: true, + }, + { + name: "AbTest_is_not_enabled_in_partner_config_abTestEnabled_is_other_than_1", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AbTestEnabled: "0", + }, + }, + }, + }, + want: false, + }, + { + name: "AbTest_is_not_enabled_in_partner_config_abTestEnabled_is_empty", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AbTestEnabled: "", + }, + }, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := CheckABTestEnabled(tt.args.rctx) + assert.Equal(t, tt.want, got) + + }) + } +} + +func TestABTestProcessing(t *testing.T) { + type args struct { + rctx models.RequestCtx + randomNumber int + } + tests := []struct { + name string + args args + want map[int]map[string]string + want1 bool + }{ + { + name: "AbTest_enabled_but_random_no_return_do_not_apply_Abtest", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AbTestEnabled: "1", + models.TestGroupSize + "_test": "0", + }, + }, + }, + }, + want: nil, + want1: false, + }, + { + name: "AbTest_is_disabled", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AbTestEnabled: "0", + models.TestGroupSize + "_test": "0", + }, + }, + }, + }, + want: nil, + want1: false, + }, + { + name: "AbTest_is_enabled_and_random_no_return_apply_AbTest", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AbTestEnabled: "1", + models.TestType + "_test": models.TestTypeAuctionTimeout, + models.SSTimeoutKey + "_test": "350", + models.TestGroupSize + "_test": "90", + models.SSTimeoutKey: "100", + }, + }, + }, + randomNumber: 50, + }, + want: map[int]map[string]string{ + -1: { + models.AbTestEnabled: "1", + models.TestType + "_test": models.TestTypeAuctionTimeout, + models.SSTimeoutKey + "_test": "350", + models.TestGroupSize + "_test": "90", + models.SSTimeoutKey: "350", + }, + }, + want1: true, + }, + { + name: "AbTest_is_enabled_and_random_no_return_apply_AbTest2", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AbTestEnabled: "1", + models.TestType + "_test": models.TestTypeAuctionTimeout, + models.SSTimeoutKey + "_test": "350", + models.TestGroupSize + "_test": "90", + models.SSTimeoutKey: "100", + }, + }, + }, + randomNumber: 95, + }, + want: nil, + want1: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + GetRandomNumberIn1To100 = func() int { + return tt.args.randomNumber + } + config, found := ABTestProcessing(tt.args.rctx) + assert.Equal(t, tt.want, config) + assert.Equal(t, tt.want1, found) + }) + } +} + +func TestApplyTestConfig(t *testing.T) { + type args struct { + rctx models.RequestCtx + val int + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "testGroupSize_is_zero_in_partner_config", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + "testGroupSize_test": "0", + }, + }, + }, + }, + want: false, + }, + { + name: "testGroupSize_in_partner_config_is_greater_than_random_number_generated", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + "testGroupSize_test": "60", + }, + }, + }, + val: 20, + }, + want: true, + }, + { + name: "testGroupSize_in_partner_config_is_equal_to_random_number_generated", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + "testGroupSize_test": "60", + }, + }, + }, + val: 60, + }, + want: true, + }, + { + name: "testGroupSize_in_partner_config_is_less_than_random_number_generated", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + "testGroupSize_test": "20", + }, + }, + }, + val: 60, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + GetRandomNumberIn1To100 = func() int { + return tt.args.val + } + got := ApplyTestConfig(tt.args.rctx) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestAppendTest(t *testing.T) { + type args struct { + key string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test", + args: args{ + key: models.AbTestEnabled, + }, + want: models.AbTestEnabled + "_test", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := AppendTest(tt.args.key) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestUpdateTestConfig(t *testing.T) { + type args struct { + rctx models.RequestCtx + } + tests := []struct { + name string + args args + want map[int]map[string]string + }{ + { + name: "testype_is_Auction_Timeout", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + AppendTest(models.TestType): models.TestTypeAuctionTimeout, + AppendTest(models.SSTimeoutKey): "350", + models.SSTimeoutKey: "100", + }, + }, + }, + }, + + want: map[int]map[string]string{ + -1: { + AppendTest(models.TestType): models.TestTypeAuctionTimeout, + AppendTest(models.SSTimeoutKey): "350", + models.SSTimeoutKey: "350", + }, + }, + }, + { + name: "testype_is_partners", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + AppendTest(models.TestType): models.TestTypePartners, + models.SSTimeoutKey: "300", + }, + 123: { + "adapterId": "201", + "adapterName": "testAdapter", + "partnerId": "101", + "partnerName": "testPartner", + "prebidPartnerName": "testPartner", + models.PartnerTestEnabledKey: "1", + "accountId_test": "1234", + "pubId_test": "8888", + "rev_share_test": "10", + "throttle_test": "100", + "serverSideEnabled_test": "1", + }, + 234: { + "adapterId": "202", + "adapterName": "SecondAdapter", + "partnerId": "102", + "partnerName": "controlPartner", + "prebidPartnerName": "controlPartner", + "rev_share": "10", + "throttle": "100", + "serverSideEnabled": "1", + }, + }, + }, + }, + + want: map[int]map[string]string{ + -1: { + AppendTest(models.TestType): models.TestTypePartners, + models.SSTimeoutKey: "300", + }, + 123: { + "adapterId": "201", + "adapterName": "testAdapter", + "partnerId": "101", + "partnerName": "testPartner", + "prebidPartnerName": "testPartner", + models.PartnerTestEnabledKey: "1", + "accountId_test": "1234", + "pubId_test": "8888", + "rev_share_test": "10", + "throttle_test": "100", + "serverSideEnabled_test": "1", + "rev_share": "10", + "throttle": "100", + "serverSideEnabled": "1", + "accountId": "1234", + "pubId": "8888", + }, + }, + }, + + { + name: "testype_is_client_side_vs._server_side_path", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + AppendTest(models.TestType): models.TestTypeClientVsServerPath, + models.SSTimeoutKey: "300", + }, + 123: { + "adapterId": "201", + "adapterName": "testAdapter", + "partnerId": "101", + "partnerName": "testPartner", + "prebidPartnerName": "testPartner", + models.PartnerTestEnabledKey: "1", + "pubId_test": "8888", + "rev_share": "10", + "throttle": "100", + "serverSideEnabled": "0", + "rev_share_test": "10", + "throttle_test": "100", + "serverSideEnabled_test": "1", + }, + 234: { + "adapterId": "202", + "adapterName": "SecondAdapter", + "partnerId": "102", + "partnerName": "controlPartner", + "prebidPartnerName": "controlPartner", + "rev_share": "10", + "throttle": "100", + "serverSideEnabled": "1", + "rev_share_test": "10", + "throttle_test": "100", + "serverSideEnabled_test": "0", + }, + }, + }, + }, + + want: map[int]map[string]string{ + -1: { + AppendTest(models.TestType): models.TestTypeClientVsServerPath, + models.SSTimeoutKey: "300", + }, + 123: { + "adapterId": "201", + "adapterName": "testAdapter", + "partnerId": "101", + "partnerName": "testPartner", + "prebidPartnerName": "testPartner", + models.PartnerTestEnabledKey: "1", + "pubId_test": "8888", + "rev_share": "10", + "throttle": "100", + "serverSideEnabled": "1", + "rev_share_test": "10", + "throttle_test": "100", + "serverSideEnabled_test": "1", + }, + 234: { + "adapterId": "202", + "adapterName": "SecondAdapter", + "partnerId": "102", + "partnerName": "controlPartner", + "prebidPartnerName": "controlPartner", + "rev_share": "10", + "throttle": "100", + "serverSideEnabled": "0", + "rev_share_test": "10", + "throttle_test": "100", + "serverSideEnabled_test": "0", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := UpdateTestConfig(tt.args.rctx) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestCopyPartnerConfigMap(t *testing.T) { + type args struct { + config map[int]map[string]string + } + tests := []struct { + name string + args args + want map[int]map[string]string + }{ + { + name: "test", + args: args{ + config: map[int]map[string]string{ + 123: { + "adapterId": "201", + "adapterName": "testAdapter", + "partnerId": "101", + "partnerName": "testPartner", + "prebidPartnerName": "testPartner", + "accountId": "1234", + "pubId": "8888", + }, + }, + }, + want: map[int]map[string]string{ + 123: { + "adapterId": "201", + "adapterName": "testAdapter", + "partnerId": "101", + "partnerName": "testPartner", + "prebidPartnerName": "testPartner", + "accountId": "1234", + "pubId": "8888", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := copyPartnerConfigMap(tt.args.config) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestReplaceControlConfig(t *testing.T) { + type args struct { + partnerConfig map[int]map[string]string + partnerID int + key string + } + tests := []struct { + name string + args args + want map[int]map[string]string + }{ + { + name: "testValue_is_present", + args: args{ + partnerConfig: map[int]map[string]string{ + -1: { + AppendTest(models.TestType): models.TestTypeAuctionTimeout, + AppendTest(models.SSTimeoutKey): "350", + models.SSTimeoutKey: "100", + }, + }, + partnerID: models.VersionLevelConfigID, + key: models.SSTimeoutKey, + }, + want: map[int]map[string]string{ + -1: { + AppendTest(models.TestType): models.TestTypeAuctionTimeout, + AppendTest(models.SSTimeoutKey): "350", + models.SSTimeoutKey: "350", + }, + }, + }, + { + name: "testValue_is_not_present", + args: args{ + partnerConfig: map[int]map[string]string{ + -1: { + AppendTest(models.TestType): models.TestTypeAuctionTimeout, + models.SSTimeoutKey: "100", + }, + }, + partnerID: models.VersionLevelConfigID, + key: models.SSTimeoutKey, + }, + want: map[int]map[string]string{ + -1: { + AppendTest(models.TestType): models.TestTypeAuctionTimeout, + models.SSTimeoutKey: "100", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + replaceControlConfig(tt.args.partnerConfig, tt.args.partnerID, tt.args.key) + assert.Equal(t, tt.want, tt.args.partnerConfig) + }) + } +} + +func TestCopyTestConfig(t *testing.T) { + type args struct { + partnerConfig map[int]map[string]string + partnerID int + key string + } + tests := []struct { + name string + args args + want map[int]map[string]string + }{ + { + name: "key_has__test_suffix", + args: args{ + partnerConfig: map[int]map[string]string{ + 123: { + "accountId_test": "1234", + }, + }, + key: "accountId_test", + partnerID: 123, + }, + want: map[int]map[string]string{ + 123: { + "accountId_test": "1234", + "accountId": "1234", + }, + }, + }, + { + name: "key_do_not_have__test_suffix", + args: args{ + partnerConfig: map[int]map[string]string{ + 123: { + "accountId": "1234", + }, + }, + key: "accountId", + partnerID: 123, + }, + want: map[int]map[string]string{ + 123: { + "accountId": "1234", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + copyTestConfig(tt.args.partnerConfig, tt.args.partnerID, tt.args.key) + assert.Equal(t, tt.want, tt.args.partnerConfig) + }) + } +} diff --git a/modules/pubmatic/openwrap/adapters/bidder_alias.go b/modules/pubmatic/openwrap/adapters/bidder_alias.go new file mode 100644 index 00000000000..ca19190fc31 --- /dev/null +++ b/modules/pubmatic/openwrap/adapters/bidder_alias.go @@ -0,0 +1,32 @@ +package adapters + +import ( + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +//ResolveOWBidder it resolves hardcoded bidder alias names + +func ResolveOWBidder(bidderName string) string { + + var coreBidderName string + + switch bidderName { + + case models.BidderAdGenerationAlias: + coreBidderName = string(openrtb_ext.BidderAdgeneration) + case models.BidderDistrictmDMXAlias: + coreBidderName = string(openrtb_ext.BidderDmx) + case models.BidderPubMaticSecondaryAlias: + coreBidderName = string(openrtb_ext.BidderPubmatic) + case models.BidderDistrictmAlias, models.BidderMediaFuseAlias: + coreBidderName = string(openrtb_ext.BidderAppnexus) + case models.BidderAndBeyondAlias: + coreBidderName = string(openrtb_ext.BidderAdkernel) + default: + coreBidderName = bidderName + + } + + return coreBidderName +} diff --git a/modules/pubmatic/openwrap/adapters/bidders.go b/modules/pubmatic/openwrap/adapters/bidders.go new file mode 100644 index 00000000000..7b177051a85 --- /dev/null +++ b/modules/pubmatic/openwrap/adapters/bidders.go @@ -0,0 +1,604 @@ +package adapters + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +// PrepareBidParamJSONForPartner preparing bid params json for partner +func PrepareBidParamJSONForPartner(width *int64, height *int64, fieldMap map[string]interface{}, slotKey, adapterName, bidderCode string, impExt *models.ImpExtension) (json.RawMessage, error) { + params := BidderParameters{ + AdapterName: adapterName, + BidderCode: bidderCode, + ImpExt: impExt, + FieldMap: fieldMap, + Width: width, + Height: height, + SlotKey: slotKey, + } + + //get callback function and execute it + callback := getBuilder(params.AdapterName) + return callback(params) +} + +// defaultBuilder for building json object for all other bidder +func defaultBuilder(params BidderParameters) (json.RawMessage, error) { + //check if ResolveOWBidder is required or not + params.AdapterName = ResolveOWBidder(params.AdapterName) + return prepareBidParamJSONDefault(params) +} + +// builderPubMatic for building json object for all other bidder +func builderPubMatic(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + jsonStr.WriteByte('{') + + //UOE-5744: Adding custom changes for hybrid profiles + //publisherID + publisherID, _ := getString(params.FieldMap["publisherId"]) + fmt.Fprintf(&jsonStr, `"publisherId":"%s"`, publisherID) + + //adSlot + if adSlot, ok := getString(params.FieldMap["adSlot"]); ok { + fmt.Fprintf(&jsonStr, `,"adSlot":"%s"`, adSlot) + } + + //pmzoneid + if pmzoneid, ok := getString(params.FieldMap["pmzoneid"]); ok { + fmt.Fprintf(&jsonStr, `,"pmzoneid":"%s"`, pmzoneid) + } + + //dctr + if dctr, ok := getString(params.FieldMap["dctr"]); ok { + fmt.Fprintf(&jsonStr, `,"dctr":"%s"`, dctr) + } + + //kadfloor + if kadfloor, ok := getString(params.FieldMap["kadfloor"]); ok { + fmt.Fprintf(&jsonStr, `,"kadfloor":"%s"`, kadfloor) + } + + //wrapper object + if value, ok := params.FieldMap["wrapper"]; ok { + if wrapper, ok := value.(map[string]interface{}); ok { + fmt.Fprintf(&jsonStr, `,"wrapper":{`) + + //profile + profile, _ := getInt(wrapper["profile"]) + fmt.Fprintf(&jsonStr, `"profile":%d`, profile) + + //version + version, _ := getInt(wrapper["version"]) + fmt.Fprintf(&jsonStr, `,"version":%d`, version) + + jsonStr.WriteByte('}') + } + } + + //keywords + if value, ok := params.FieldMap["keywords"]; ok { + if keywords, err := json.Marshal(value); err == nil { + fmt.Fprintf(&jsonStr, `,"keywords":%s`, string(keywords)) + } + } + + //bidViewability Object + if value, ok := params.FieldMap["bidViewability"]; ok { + if bvsJson, err := json.Marshal(value); err == nil { + fmt.Fprintf(&jsonStr, `,"bidViewability":%s`, string(bvsJson)) + } + } + + jsonStr.WriteByte('}') + + return jsonStr.Bytes(), nil +} + +// builderAppNexus for building json object for AppNexus bidder +func builderAppNexus(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + jsonStr.WriteByte('{') + + //incase if placementId not present then fallback to placement_id else log 0 + placementID, ok := getInt(params.FieldMap["placementId"]) + if !ok { + placementID, _ = getInt(params.FieldMap["placement_id"]) + } + fmt.Fprintf(&jsonStr, `"placementId":%d`, placementID) + + //reserve parameter + if reserve, ok := getFloat64(params.FieldMap["reserve"]); ok { + fmt.Fprintf(&jsonStr, `,"reserve":%.3f`, reserve) + } + + //use_pmt_rule parameter + usePaymentRule, ok := getBool(params.FieldMap["usePaymentRule"]) + if !ok { + usePaymentRule, ok = getBool(params.FieldMap["use_pmt_rule"]) + } + if ok { + fmt.Fprintf(&jsonStr, `,"use_pmt_rule":%t`, usePaymentRule) + } + + //anyone invcode and member + invCode, ok := getString(params.FieldMap["invCode"]) + if !ok { + invCode, ok = getString(params.FieldMap["inv_code"]) + } + if ok { + fmt.Fprintf(&jsonStr, `,"invCode":"%s"`, invCode) + } else { + if member, ok := getString(params.FieldMap["member"]); ok { + fmt.Fprintf(&jsonStr, `,"member":"%s"`, member) + } + } + + //keywords + if val, ok := params.FieldMap["keywords"]; ok { + //UOE-5744: Adding custom changes for hybrid profiles + if keywords, _ := json.Marshal(val); len(keywords) > 0 { + fmt.Fprintf(&jsonStr, `,"keywords":%s`, string(keywords)) + } + } else if keywords := getKeywordStringForPartner(params.ImpExt, params.BidderCode); keywords != "" { + fmt.Fprintf(&jsonStr, `,"keywords":%s`, keywords) + } + + //generate_ad_pod_id + if generateAdPodID, ok := getBool(params.FieldMap["generate_ad_pod_id"]); ok { + fmt.Fprintf(&jsonStr, `,"generate_ad_pod_id":%t`, generateAdPodID) + } + + //other parameters + for key, val := range params.FieldMap { + if ignoreAppnexusKeys[key] { + continue + } + if strVal, ok := getString(val); ok { + fmt.Fprintf(&jsonStr, `,"%s":"%s"`, key, strVal) + } + } + + jsonStr.WriteByte('}') + + return jsonStr.Bytes(), nil +} + +// builderIndex for building json object for Index bidder +func builderIndex(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + + siteID, ok := getString(params.FieldMap["siteID"]) + if !ok { + //UOE-5744: Adding custom changes for hybrid profiles + if siteID, ok = getString(params.FieldMap["siteId"]); !ok { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "siteID") + } + } + + width, height := params.Width, params.Height + if width == nil || height == nil { + //UOE-5744: Adding custom changes for hybrid profiles + size, ok := getIntArray(params.FieldMap["size"]) + if len(size) != 2 || !ok { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "size") + } + w := int64(size[0]) + h := int64(size[1]) + width, height = &w, &h + } + + fmt.Fprintf(&jsonStr, `{"siteId":"%s","size":[%d,%d]}`, siteID, *width, *height) + return jsonStr.Bytes(), nil +} + +// builderPulsePoint for building json object for PulsePoint bidder +func builderPulsePoint(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + + cp, _ := getInt(params.FieldMap["cp"]) + ct, _ := getInt(params.FieldMap["ct"]) + cf, _ := getString(params.FieldMap["cf"]) + //[UOE-5744]: read adsize from fieldmap itself + + if len(cf) == 0 { + cf = "0x0" + adSlot := strings.Split(params.SlotKey, "@") + if len(adSlot) == 2 && adSlot[0] != "" && adSlot[1] != "" { + cf = adSlot[1] + } + } + + fmt.Fprintf(&jsonStr, `{"cp":%d,"ct":%d,"cf":"%s"}`, cp, ct, cf) + return jsonStr.Bytes(), nil +} + +// builderRubicon for building json object for Rubicon bidder +func builderRubicon(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + jsonStr.WriteByte('{') + + if accountID, ok := getInt(params.FieldMap["accountId"]); ok { + fmt.Fprintf(&jsonStr, `"accountId":%d,`, accountID) + } + + if siteID, ok := getInt(params.FieldMap["siteId"]); ok { + fmt.Fprintf(&jsonStr, `"siteId":%d,`, siteID) + } + + if zoneID, ok := getInt(params.FieldMap["zoneId"]); ok { + fmt.Fprintf(&jsonStr, `"zoneId":%d,`, zoneID) + } + + if _, ok := params.FieldMap["video"]; ok { + if videoMap, ok := (params.FieldMap["video"]).(map[string]interface{}); ok { + jsonStr.WriteString(`"video":{`) + + if width, ok := getInt(videoMap["playerWidth"]); ok { + fmt.Fprintf(&jsonStr, `"playerWidth":%d,`, width) + } + + if height, ok := getInt(videoMap["playerHeight"]); ok { + fmt.Fprintf(&jsonStr, `"playerHeight":%d,`, height) + } + + if sizeID, ok := getInt(videoMap["size_id"]); ok { + fmt.Fprintf(&jsonStr, `"size_id":%d,`, sizeID) + } + + if lang, ok := getString(videoMap["language"]); ok { + fmt.Fprintf(&jsonStr, `"language":"%s",`, lang) + } + + trimComma(&jsonStr) + jsonStr.WriteString(`},`) + } + } + + trimComma(&jsonStr) + jsonStr.WriteByte('}') + + return jsonStr.Bytes(), nil +} + +// builderOpenx for building json object for Openx bidder +func builderOpenx(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + jsonStr.WriteByte('{') + + if delDomain, ok := getString(params.FieldMap["delDomain"]); ok { + fmt.Fprintf(&jsonStr, `"delDomain":"%s",`, delDomain) + } else { + } + + if unit, ok := getString(params.FieldMap["unit"]); ok { + fmt.Fprintf(&jsonStr, `"unit":"%s"`, unit) + } else { + } + + trimComma(&jsonStr) + jsonStr.WriteByte('}') + return jsonStr.Bytes(), nil +} + +// builderSovrn for building json object for Sovrn bidder +func builderSovrn(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + jsonStr.WriteByte('{') + + if tagID, ok := getString(params.FieldMap["tagid"]); ok { + fmt.Fprintf(&jsonStr, `"tagid":"%s",`, tagID) + } else { + } + + if bidFloor, ok := getFloat64(params.FieldMap["bidfloor"]); ok { + fmt.Fprintf(&jsonStr, `"bidfloor":%f`, bidFloor) + } + + trimComma(&jsonStr) + jsonStr.WriteByte('}') + + return jsonStr.Bytes(), nil +} + +// builderImproveDigital for building json object for ImproveDigital bidder +func builderImproveDigital(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + jsonStr.WriteByte('{') + + if placementID, ok := getInt(params.FieldMap["placementId"]); ok { + fmt.Fprintf(&jsonStr, `"placementId":%d`, placementID) + } else { + publisherID, ok1 := getInt(params.FieldMap["publisherId"]) + placement, ok2 := getString(params.FieldMap["placementKey"]) + if !ok1 || !ok2 { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "['placementId'] or ['publisherId', 'placementKey']") + } + fmt.Fprintf(&jsonStr, `"publisherId":%d,"placementKey":"%s"`, publisherID, placement) + } + + width, height := params.Width, params.Height + ////UOE-5744: Adding custom changes for hybrid profiles + if val, ok := params.FieldMap["size"]; ok { + if size, ok := val.(map[string]interface{}); ok { + w, ok1 := getInt(size["w"]) + h, ok2 := getInt(size["h"]) + if ok1 && ok2 { + _w := int64(w) + _h := int64(h) + width = &(_w) + height = &(_h) + } + } + } + if width != nil && height != nil { + fmt.Fprintf(&jsonStr, `,"size":{"w":%d,"h":%d}`, *width, *height) + } + + jsonStr.WriteByte('}') + return jsonStr.Bytes(), nil +} + +// builderBeachfront for building json object for Beachfront bidder +func builderBeachfront(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + jsonStr.WriteByte('{') + + if appID, ok := getString(params.FieldMap["appId"]); !ok { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "appId") + } else { + fmt.Fprintf(&jsonStr, `"appId":"%s",`, appID) + } + + if bidfloor, ok := getFloat64(params.FieldMap["bidfloor"]); !ok { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "bidfloor") + } else { + fmt.Fprintf(&jsonStr, `"bidfloor":%f`, bidfloor) + } + + //As per beachfront bidder parameter documentation, by default the video response will be a nurl URL. + //OpenWrap platform currently only consumes 'adm' responses so setting hardcoded value 'adm' for videoResponseType. + jsonStr.WriteString(`,"videoResponseType":"adm"`) + + jsonStr.WriteByte('}') + return jsonStr.Bytes(), nil +} + +// builderSmaato for building json object for Smaato bidder +func builderSmaato(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + jsonStr.WriteByte('{') + + if publisherID, ok := getString(params.FieldMap["publisherId"]); !ok { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "publisherId") + } else { + fmt.Fprintf(&jsonStr, `"publisherId":"%s",`, publisherID) + } + + if adspaceID, ok := getString(params.FieldMap["adspaceId"]); !ok { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "adspaceId") + } else { + fmt.Fprintf(&jsonStr, `"adspaceId":"%s"`, adspaceID) + } + + jsonStr.WriteByte('}') + return jsonStr.Bytes(), nil +} + +// builderSmartAdServer for building json object for SmartAdServer bidder +func builderSmartAdServer(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + jsonStr.WriteByte('{') + + if networkID, ok := getInt(params.FieldMap["networkId"]); !ok { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "networkId") + } else { + fmt.Fprintf(&jsonStr, `"networkId":%d`, networkID) + } + + // siteId, pageId and formatId are dependent on each other and hence need to be sent only when all three are present + siteID, isSiteIDPresent := getInt(params.FieldMap["siteId"]) + pageID, isPageIDPresent := getInt(params.FieldMap["pageId"]) + formatID, isFormatIDPresent := getInt(params.FieldMap["formatId"]) + + if isSiteIDPresent && isPageIDPresent && isFormatIDPresent { + // all three are valid integers + fmt.Fprintf(&jsonStr, `,"siteId":%d,"pageId":%d,"formatId":%d`, siteID, pageID, formatID) + } else { + } + + jsonStr.WriteByte('}') + return jsonStr.Bytes(), nil +} + +// builderGumGum for building json object for GumGum bidder +func builderGumGum(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + + if zone, ok := getString(params.FieldMap["zone"]); !ok { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "zone") + } else { + fmt.Fprintf(&jsonStr, `{"zone":"%s"}`, zone) + } + + return jsonStr.Bytes(), nil +} + +// builderPangle for building json object for Pangle bidder +func builderPangle(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + + token, ok := getString(params.FieldMap["token"]) + if !ok { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "token") + } + + appID, appIDPresent := getString(params.FieldMap["appid"]) + placementID, placementIDPresent := getString(params.FieldMap["placementid"]) + + if appIDPresent && !placementIDPresent { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "placementid") + } else if !appIDPresent && placementIDPresent { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "appid") + } + + if appIDPresent && placementIDPresent { + fmt.Fprintf(&jsonStr, `{"token":"%s","placementid":"%s","appid":"%s"}`, token, placementID, appID) + } else { + fmt.Fprintf(&jsonStr, `{"token":"%s"}`, token) + } + + return jsonStr.Bytes(), nil +} + +// builderSonobi for building json object for Sonobi bidder +func builderSonobi(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + + tagID, _ := getString(params.FieldMap["ad_unit"]) //checking with ad_unit value + if len(tagID) == 0 { + tagID, _ = getString(params.FieldMap["placement_id"]) //checking with placement_id + } + + if len(tagID) == 0 { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "'ad_unit' or 'placement_id'") + } + + fmt.Fprintf(&jsonStr, `{"TagID":"%s"}`, tagID) + return jsonStr.Bytes(), nil +} + +// builderAdform for building json object for Adform bidder +func builderAdform(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + + if mid, ok := getInt(params.FieldMap["mid"]); ok { + fmt.Fprintf(&jsonStr, `{"mid":%d}`, mid) + } else { + inv, invPresent := getInt(params.FieldMap["inv"]) + mname, mnamePresent := getString(params.FieldMap["mname"]) + + if !(invPresent && mnamePresent) { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, "'mid' and 'inv'") + } + + fmt.Fprintf(&jsonStr, `{"inv":%d,"mname":"%s"}`, inv, mname) + } + + return jsonStr.Bytes(), nil +} + +// builderCriteo for building json object for Criteo bidder +func builderCriteo(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + + anyOf := []string{"zoneId", "networkId"} // not checking zoneid and networkid as client side uses only zoneId and networkId + for _, param := range anyOf { + if val, ok := getInt(params.FieldMap[param]); ok { + fmt.Fprintf(&jsonStr, `{"%s":%d}`, param, val) + break + } + } + + if jsonStr.Len() == 0 { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, anyOf) + } + + return jsonStr.Bytes(), nil +} + +// builderOutbrain for building json object for Outbrain bidder +func builderOutbrain(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + publisherMap, ok := params.FieldMap["publisher"] + if !ok { + return nil, nil + } + + publisher, ok := publisherMap.(map[string]interface{}) + if !ok { + return nil, nil + } + + id, ok := getString(publisher["id"]) + if !ok { + return nil, nil + } + + fmt.Fprintf(&jsonStr, `{"publisher":{"id":"%s"}}`, id) + return jsonStr.Bytes(), nil +} + +func builderApacdex(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + jsonStr.WriteByte('{') + anyOf := []string{BidderParamApacdex_siteId, BidderParamApacdex_placementId} + for _, param := range anyOf { + if key, ok := getString(params.FieldMap[param]); ok { + fmt.Fprintf(&jsonStr, `"%s":"%s"`, param, key) + break + } + } + // len=1 (no mandatory params present) + if jsonStr.Len() == 1 { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, anyOf) + } + if floorPrice, ok := getFloat64(params.FieldMap[BidderParamApacdex_floorPrice]); ok { + fmt.Fprintf(&jsonStr, `,"%s":%g`, BidderParamApacdex_floorPrice, floorPrice) + } + //geo object(hybrid param) + if value, ok := params.FieldMap[BidderParamApacdex_geo]; ok { + if geoJson, err := json.Marshal(value); err == nil { + fmt.Fprintf(&jsonStr, `,"%s":%s`, BidderParamApacdex_geo, string(geoJson)) + } + } + jsonStr.WriteByte('}') + return jsonStr.Bytes(), nil +} + +func builderUnruly(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + jsonStr.WriteByte('{') + anyOf := []string{"siteId", "siteid"} + for _, param := range anyOf { + if key, ok := getInt(params.FieldMap[param]); ok { + fmt.Fprintf(&jsonStr, `"%s":%d`, param, key) + break + } + } + // len=0 (no mandatory params present) + if jsonStr.Len() == 1 { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, anyOf) + } + + if value, ok := params.FieldMap["featureOverrides"]; ok { + if featureOverridesJson, err := json.Marshal(value); err == nil { + fmt.Fprintf(&jsonStr, `,"%s":%s`, "featureOverrides", string(featureOverridesJson)) + } + } + jsonStr.WriteByte('}') + return jsonStr.Bytes(), nil +} + +func builderBoldwin(params BidderParameters) (json.RawMessage, error) { + jsonStr := bytes.Buffer{} + jsonStr.WriteByte('{') + oneOf := []string{BidderParamBoldwinPlacementID, BidderParamBoldwinEndpointID} + for _, param := range oneOf { + if key, ok := getString(params.FieldMap[param]); ok { + fmt.Fprintf(&jsonStr, `"%s":"%s"`, param, key) + break + } + } + // len=0 (no mandatory params present) + if jsonStr.Len() == 1 { + return nil, fmt.Errorf(errMandatoryParameterMissingFormat, params.AdapterName, oneOf) + } + + jsonStr.WriteByte('}') + return jsonStr.Bytes(), nil +} diff --git a/modules/pubmatic/openwrap/adapters/builder.go b/modules/pubmatic/openwrap/adapters/builder.go new file mode 100644 index 00000000000..e13bc0af483 --- /dev/null +++ b/modules/pubmatic/openwrap/adapters/builder.go @@ -0,0 +1,75 @@ +package adapters + +import ( + "encoding/json" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// BidderParameters provides all properties requires for bidder to generate bidder json +type BidderParameters struct { + //AdapterName, BidderCode should be passed in builder function + ReqID string + AdapterName, BidderCode string + ImpExt *models.ImpExtension + + //bidder specific parameters + FieldMap JSONObject + Width, Height *int64 + SlotKey string +} + +// JSONObject generic JSON object +type JSONObject = map[string]interface{} + +// builder callback type +type builder func(params BidderParameters) (json.RawMessage, error) + +// bidderBuilderFactor +var _bidderBuilderFactory map[string]builder + +// initBidderBuilderFactory initialise all hard coded bidder builder +func initBidderBuilderFactory() { + _bidderBuilderFactory = map[string]builder{ + string(openrtb_ext.BidderAdf): builderAdform, + string(openrtb_ext.BidderAppnexus): builderAppNexus, + string(openrtb_ext.BidderBeachfront): builderBeachfront, + string(openrtb_ext.BidderCriteo): builderCriteo, + string(openrtb_ext.BidderGumGum): builderGumGum, + string(openrtb_ext.BidderImprovedigital): builderImproveDigital, + string(openrtb_ext.BidderIx): builderIndex, + string(openrtb_ext.BidderOpenx): builderOpenx, + string(openrtb_ext.BidderOutbrain): builderOutbrain, + string(openrtb_ext.BidderPangle): builderPangle, + string(openrtb_ext.BidderPubmatic): builderPubMatic, /*this only gets used incase of hybrid case*/ + string(openrtb_ext.BidderPulsepoint): builderPulsePoint, + string(openrtb_ext.BidderRubicon): builderRubicon, + string(openrtb_ext.BidderSmaato): builderSmaato, + string(openrtb_ext.BidderSmartAdserver): builderSmartAdServer, + string(openrtb_ext.BidderSonobi): builderSonobi, + string(openrtb_ext.BidderSovrn): builderSovrn, + string(openrtb_ext.BidderApacdex): builderApacdex, + string(openrtb_ext.BidderUnruly): builderUnruly, + string(openrtb_ext.BidderMediafuse): builderAppNexus, + string(openrtb_ext.BidderBoldwin): builderBoldwin, + } +} + +// getBuilder will return core bidder hard coded builder, if not found then returns default builder +func getBuilder(adapterName string) builder { + //resolve hardcoded bidder alias + adapterName = ResolveOWBidder(adapterName) + + if callback, ok := _bidderBuilderFactory[adapterName]; ok { + return callback + } + return defaultBuilder +} + +// InitBidders will initialise bidder alias, default bidder parameter json and builders for each bidder +func InitBidders(cfg config.Config) error { + initBidderBuilderFactory() + return parseBidderParams(cfg) +} diff --git a/modules/pubmatic/openwrap/adapters/constant.go b/modules/pubmatic/openwrap/adapters/constant.go new file mode 100644 index 00000000000..d8bf27d379c --- /dev/null +++ b/modules/pubmatic/openwrap/adapters/constant.go @@ -0,0 +1,32 @@ +package adapters + +const ( + errMandatoryParameterMissingFormat = `adapter:[%s] message:[missing_mandatory_param] key:[%v]` + errInvalidS2SPartnerFormat = `adapter:[%s] message:[invalid_s2s_adapter] slotkey:[%s]` + errDefaultBidderParameterMissingFormat = `adapter:[%s] message:[default_bidder_missing_manadatory_param] param:[%s] applicable-key:[%s]` +) + +var ignoreAppnexusKeys = map[string]bool{ + "generate_ad_pod_id": true, + "invCode": true, + "inv_code": true, + "keywords": true, + "member": true, + "placementId": true, + "placement_id": true, + "private_sizes": true, + "reserve": true, + "usePaymentRule": true, + "use_pmt_rule": true, + "video": true, +} + +// Bidder Params +const ( + BidderParamApacdex_siteId = "siteId" + BidderParamApacdex_placementId = "placementId" + BidderParamApacdex_geo = "geo" + BidderParamApacdex_floorPrice = "floorPrice" + BidderParamBoldwinPlacementID = "placementId" + BidderParamBoldwinEndpointID = "endpointId" +) diff --git a/modules/pubmatic/openwrap/adapters/converter.go b/modules/pubmatic/openwrap/adapters/converter.go new file mode 100644 index 00000000000..3bc2ad067ad --- /dev/null +++ b/modules/pubmatic/openwrap/adapters/converter.go @@ -0,0 +1,38 @@ +package adapters + +import ( + "encoding/json" +) + +// convertExtToFieldMap converts bidder json parameter to object +func convertExtToFieldMap(bidderName string, ext json.RawMessage) JSONObject { + fieldmap := JSONObject{} + if err := json.Unmarshal(ext, &fieldmap); err != nil { + } + return fieldmap +} + +// FixBidderParams will fixes bidder parameter types for prebid auction endpoint(UOE-5744) +func FixBidderParams(reqID, adapterName, bidderCode string, ext json.RawMessage) (json.RawMessage, error) { + /* + //check if fixing bidder parameters really required + if err := router.GetBidderParamValidator().Validate(openrtb_ext.BidderName(bidderCode), ext); err == nil { + //fixing bidder parameter datatype is not required + return ext, nil + } + */ + + //convert jsonstring to jsonobj + fieldMap := convertExtToFieldMap(bidderCode, ext) + + //get callback function and execute it + callback := getBuilder(adapterName) + + //executing callback function + return callback(BidderParameters{ + ReqID: reqID, + AdapterName: adapterName, //actual partner name + BidderCode: bidderCode, //alias bidder name + FieldMap: fieldMap, + }) +} diff --git a/modules/pubmatic/openwrap/adapters/default_bidder.go b/modules/pubmatic/openwrap/adapters/default_bidder.go new file mode 100644 index 00000000000..d79ec1f09d8 --- /dev/null +++ b/modules/pubmatic/openwrap/adapters/default_bidder.go @@ -0,0 +1,189 @@ +package adapters + +import ( + "encoding/json" + "errors" + "fmt" + + "strconv" + "strings" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +// Map containing []ParameterMapping for all partners (partner name) +var adapterParams map[string]map[string]*ParameterMapping + +func prepareBidParamJSONDefault(params BidderParameters) (json.RawMessage, error) { + bidderParamMapping, present := adapterParams[params.AdapterName] + if !present { + return nil, fmt.Errorf(errInvalidS2SPartnerFormat, params.AdapterName, params.SlotKey) + } + + bidderParams := make(map[string]interface{}) + for _, mapping := range bidderParamMapping { + paramValue, present := params.FieldMap[mapping.KeyName] + if !present && mapping.DefaultValue != nil { + present = true + paramValue = mapping.DefaultValue + } + + if !present && mapping.Required { + return nil, fmt.Errorf(errDefaultBidderParameterMissingFormat, params.AdapterName, mapping.BidderParamName, mapping.KeyName) + } + + if present { + err := addBidParam(bidderParams, mapping.BidderParamName, mapping.Datatype, paramValue) + if err != nil && mapping.Required { + return nil, err + } + } + } + + jsonBuf, err := json.Marshal(bidderParams) + if err != nil { + return nil, err + } + + return jsonBuf, nil +} + +func addBidParam(bidParams map[string]interface{}, name string, paramType string, value interface{}) error { + dataType := getDataType(paramType) + + switch dataType { + case models.DataTypeInteger: + //DataTypeInteger + intVal, err := strconv.Atoi(fmt.Sprintf("%v", value)) + if err != nil { + return err + } + bidParams[name] = intVal + case models.DataTypeFloat: + //DataTypeFloat + floatVal, err := strconv.ParseFloat(fmt.Sprintf("%v", value), 64) + if err != nil { + return err + } + bidParams[name] = toFixed(floatVal, FloatValuePrecision) + case models.DataTypeString: + //DataTypeString + val := fmt.Sprintf("%v", value) + if val == "" { + return errors.New("value is empty") + } + bidParams[name] = fmt.Sprintf("%v", value) + case models.DataTypeBoolean: + //DataTypeBoolean + boolVal, err := strconv.ParseBool(fmt.Sprintf("%v", value)) + if err != nil { + return err + } + bidParams[name] = boolVal + case models.DataTypeArrayOfIntegers: + //Array of DataTypeInteger + switch v := value.(type) { + case string: + var arr []int + err := json.Unmarshal([]byte(value.(string)), &arr) + if err != nil { + return err + } + bidParams[name] = arr + case []int: + bidParams[name] = v + case []interface{}: + //Unmarshal's default type for array. Refer https://pkg.go.dev/encoding/json#Unmarshal + arr := make([]int, 0, len(v)) + for _, elem := range v { + elemFloat, ok := elem.(float64) //Unmarshal's default type interface values + if !ok { + return fmt.Errorf("ErrTypeCastFailed %s float64 %v", name, elem) + } + arr = append(arr, int(elemFloat)) + } + + bidParams[name] = arr + default: + errMsg := fmt.Sprintf("unknown array type %T!\n", v) + return errors.New(errMsg) + } + case models.DataTypeArrayOfFloats: + //Array of DataTypeFloat + switch v := value.(type) { + case string: + var arr []float64 + err := json.Unmarshal([]byte(value.(string)), &arr) + if err != nil { + return err + } + bidParams[name] = arr + case []float64: + bidParams[name] = v + case []interface{}: + //Unmarshal's default type for array. Refer https://pkg.go.dev/encoding/json#Unmarshal + arr := make([]float64, 0, len(v)) + for _, elem := range v { + elemFloat, ok := elem.(float64) //Unmarshal's default type interface values + if !ok { + return fmt.Errorf("ErrTypeCastFailed %s float64 %v", name, elem) + } + arr = append(arr, elemFloat) + } + + bidParams[name] = arr + default: + errMsg := fmt.Sprintf("unknown array type %T!\n", v) + return errors.New(errMsg) + } + case models.DataTypeArrayOfStrings: + //Array of DataTypeString + switch v := value.(type) { + case string: + var arr []string + stringValue := strings.Trim(value.(string), "[]") + arr = strings.Split(stringValue, ",") + bidParams[name] = arr + case []string: + bidParams[name] = v + case []interface{}: + arr := make([]string, 0, len(v)) + for _, elem := range v { + elemStr, ok := elem.(string) + if !ok { + return fmt.Errorf("ErrTypeCastFailed %s float64 %v", name, elem) + } + arr = append(arr, elemStr) + } + bidParams[name] = arr + default: + errMsg := fmt.Sprintf("unknown array type %T!\n", v) + return errors.New(errMsg) + } + default: + bidParams[name] = fmt.Sprintf("%v", value) + } + + return nil +} + +func getDataType(paramType string) int { + switch paramType { + case "string": + return models.DataTypeString + case "number": + return models.DataTypeFloat + case "integer": + return models.DataTypeInteger + case "boolean": + return models.DataTypeBoolean + case "[]string": + return models.DataTypeArrayOfStrings + case "[]integer": + return models.DataTypeArrayOfIntegers + case "[]number": + return models.DataTypeArrayOfFloats + default: + return models.DataTypeUnknown + } +} diff --git a/modules/pubmatic/openwrap/adapters/default_bidder_parameter.go b/modules/pubmatic/openwrap/adapters/default_bidder_parameter.go new file mode 100644 index 00000000000..20572c8d60f --- /dev/null +++ b/modules/pubmatic/openwrap/adapters/default_bidder_parameter.go @@ -0,0 +1,210 @@ +package adapters + +import ( + "encoding/json" + "errors" + "fmt" + + "os" + "path/filepath" + "strings" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// BidderParamJSON defines type as per JSON schema files in static/bidder-param +type BidderParamJSON struct { + Title string `json:"title"` + Properties map[string]BidderParameter `json:"properties"` + Required []string `json:"required"` + OneOf interface{} `json:"oneOf"` + Not interface{} `json:"not"` + AnyOf interface{} `json:"anyOf"` + Dependencies interface{} `json:"dependencies"` +} + +// BidderParameter defines properties type as per JSON schema files in static/bidder-param +type BidderParameter struct { + Type interface{} `json:"type"` + Items ArrayItemsType `json:"items"` +} + +// ParameterMapping holds mapping information for bidder parameter +type ParameterMapping struct { + BidderParamName string `json:"bidderParameterName,omitempty"` + KeyName string `json:"keyName,omitempty"` + Datatype string `json:"type,omitempty"` + Required bool `json:"required,omitempty"` + DefaultValue interface{} `json:"defaultValue,omitempty"` +} + +// ArrayItemsType defines items type as per JSON schema files in static/bidder-param +type ArrayItemsType struct { + Type string `json:"type"` +} + +func parseBidderParams(cfg config.Config) error { + schemas, err := parseBidderSchemaDefinitions() + if err != nil { + return err + } + + owParameterMappings := parseOpenWrapParameterMappings() + if owParameterMappings == nil { + return errors.New("BidderParamMapping is not defined in config") + } + + adapterParams = make(map[string]map[string]*ParameterMapping) + + for bidderName, jsonSchema := range schemas { + + if jsonSchema.OneOf != nil || jsonSchema.AnyOf != nil || jsonSchema.Not != nil || jsonSchema.Dependencies != nil { + //JSON schema definition is complex and we rely on case block for this bidder + continue + } + + parameters := make(map[string]*ParameterMapping) + for propertyName, propertyDef := range jsonSchema.Properties { + bidderParam := ParameterMapping{} + bidderParam.BidderParamName = propertyName + bidderParam.KeyName = propertyName + bidderParam.Datatype = getType(propertyDef) + bidderParam.Required = false + + parameters[propertyName] = &bidderParam + } + + owParameterOverrides := owParameterMappings[bidderName] + for propertyName, propertyDef := range owParameterOverrides { + if parameters[propertyName] != nil { + parameter := parameters[propertyName] + if propertyDef.BidderParamName != "" { + parameter.BidderParamName = propertyDef.BidderParamName + } + if propertyDef.KeyName != "" { + parameter.KeyName = propertyDef.KeyName + } + if propertyDef.Datatype != "" { + parameter.Datatype = propertyDef.Datatype + } + if propertyDef.DefaultValue != nil { + parameter.DefaultValue = propertyDef.DefaultValue + } + parameter.Required = propertyDef.Required + } else { + } + } + + for _, propertyName := range jsonSchema.Required { + if parameters[propertyName] != nil { + parameters[propertyName].Required = true + } else { + } + } + + adapterParams[bidderName] = parameters + } + + return nil +} + +func getType(param BidderParameter) string { + tp := "" + switch param.Type.(type) { + case string: + tp = param.Type.(string) + case []string: + v := param.Type.([]string) + tp = v[0] + for _, typ := range v { + if typ == "string" { + tp = "string" + } + } + } + if tp == "array" { + tp = fmt.Sprintf("[]%s", param.Items.Type) + } + return tp +} + +func parseBidderSchemaDefinitions() (map[string]*BidderParamJSON, error) { + schemas := make(map[string]*BidderParamJSON) + + schemaDirectory := getBidderParamsDirectory() + if schemaDirectory == "" { + return schemas, errors.New("error failed to parse bidder params files") + } + + fileInfos, err := os.ReadDir(schemaDirectory) + if err != nil { + return schemas, errors.New("error failed to parse bidder params files" + err.Error()) + } + + bidderMap := openrtb_ext.BuildBidderMap() + + for _, fileInfo := range fileInfos { + bidderName := strings.TrimSuffix(fileInfo.Name(), ".json") + if _, isValid := bidderMap[bidderName]; !isValid { + continue + } + _, err := filepath.Abs(filepath.Join(schemaDirectory, fileInfo.Name())) + if err != nil { + continue + } + fileBytes, err := os.ReadFile(fmt.Sprintf("%s/%s", schemaDirectory, fileInfo.Name())) + if err != nil { + continue + } + + var bidderParamJSON BidderParamJSON + err = json.Unmarshal(fileBytes, &bidderParamJSON) + if err != nil { + continue + } + + schemas[bidderName] = &bidderParamJSON + } + + if len(schemas) == 0 { + return schemas, errors.New("Error failed to parse bidder params files") + } + + return schemas, nil +} + +func getBidderParamsDirectory() string { + schemaDirectory := "./static/bidder-params" + if isDirectoryExists(schemaDirectory) { + return schemaDirectory + } + + return "" +} + +func parseOpenWrapParameterMappings() map[string]map[string]*ParameterMapping { + return map[string]map[string]*ParameterMapping{ + "dmx": { + "tagid": { + KeyName: "dmxid", + }, + }, + "vrtcal": { + "just_an_unused_vrtcal_param": { + KeyName: "dummyParam", + DefaultValue: "1", + }, + }, + "grid": { + "uid": { + Required: true, + }, + }, + "adkernel": { + "zoneId": { + Datatype: "integer", + }, + }, + } +} diff --git a/modules/pubmatic/openwrap/adapters/pubmatic.go b/modules/pubmatic/openwrap/adapters/pubmatic.go new file mode 100644 index 00000000000..5f9bc4514cd --- /dev/null +++ b/modules/pubmatic/openwrap/adapters/pubmatic.go @@ -0,0 +1 @@ +package adapters diff --git a/modules/pubmatic/openwrap/adapters/tests/hybrid_bidders.json b/modules/pubmatic/openwrap/adapters/tests/hybrid_bidders.json new file mode 100644 index 00000000000..02f3ba27a5e --- /dev/null +++ b/modules/pubmatic/openwrap/adapters/tests/hybrid_bidders.json @@ -0,0 +1,352 @@ +[ + { + "name": "pubmatic_client_json", + "args": { + "adapterName": "pubmatic", + "requestJSON": { + "wiid": "147dc5a6-cef1-4733-b7f4-fd4447bf9393-difid", + "publisherId": "5890", + "adSlot": "/43743431/DMDemo@728x90:0", + "kadfloor": "1.0", + "wrapper": { + "profile": 8671, + "version": 4 + } + } + }, + "want": { + "expectedJSON": { + "adSlot": "/43743431/DMDemo@728x90:0", + "publisherId": "5890", + "kadfloor": "1.0", + "wrapper": { + "profile": 8671, + "version": 4 + } + } + } + }, + { + "name": "appnexus_client_json", + "args": { + "adapterName": "appnexus", + "requestJSON": { + "placementId": "9880618" + } + }, + "want": { + "expectedJSON": { + "placementId": 9880618 + } + } + }, + { + "name": "districtm_client_json", + "args": { + "adapterName": "districtm", + "requestJSON": { + "placementId": "9880618" + } + }, + "want": { + "expectedJSON": { + "placementId": 9880618 + } + } + }, + { + "name": "ix_client_json", + "args": { + "adapterName": "ix", + "requestJSON": { + "siteId": "171906", + "id": "123", + "size": [ + 160, + 600 + ] + } + }, + "want": { + "expectedJSON": { + "siteId": "171906", + "size": [ + 160, + 600 + ] + } + } + }, + { + "name": "pulsepoint_client_json", + "args": { + "adapterName": "pulsepoint", + "requestJSON": { + "ct": "1300", + "cp": "521732", + "cf": "160x600" + } + }, + "want": { + "expectedJSON": { + "cp": 521732, + "ct": 1300, + "cf": "160x600" + } + } + }, + { + "name": "rubicon_client_json", + "args": { + "adapterName": "rubicon", + "requestJSON": { + "zoneId": "498816", + "siteId": "70608", + "video": { + "playerHeight": "360", + "size_id": "201", + "playerWidth": "640", + "language": "en" + }, + "accountId": "97531" + } + }, + "want": { + "expectedJSON": { + "accountId": 97531, + "siteId": 70608, + "zoneId": 498816, + "video": { + "playerWidth": 640, + "playerHeight": 360, + "size_id": 201, + "language": "en" + } + } + } + }, + { + "name": "openx_client_json", + "args": { + "adapterName": "openx", + "requestJSON": { + "unit": "539439964", + "delDomain": "se-demo-d.openx.net" + } + }, + "want": { + "expectedJSON": { + "delDomain": "se-demo-d.openx.net", + "unit": "539439964" + } + } + }, + { + "name": "sovrn_client_json", + "args": { + "adapterName": "sovrn", + "requestJSON": { + "tagid": "14", + "bidfloor": 1.2 + } + }, + "want": { + "expectedJSON": { + "tagid": "14", + "bidfloor": 1.200000 + } + } + }, + { + "name": "improvedigital_client_json", + "args": { + "adapterName": "improvedigital", + "requestJSON": { + "placementId": "1234567", + "publisherId": "5890", + "size": { + "w": 720, + "h": 120 + } + } + }, + "want": { + "expectedJSON": { + "placementId": 1234567, + "size": { + "w": 720, + "h": 120 + } + } + } + }, + { + "name": "beachfront_client_json", + "args": { + "adapterName": "beachfront", + "requestJSON": { + "appId": "11bc5dd5-7421-4dd8-c926-40fa653bec83", + "bidfloor": "0.01" + } + }, + "want": { + "expectedJSON": { + "appId": "11bc5dd5-7421-4dd8-c926-40fa653bec83", + "bidfloor": 0.01, + "videoResponseType": "adm" + } + } + }, + { + "name": "smaato_client_json", + "args": { + "adapterName": "smaato", + "requestJSON": { + "app": { + "geo": { + "lon": -88.80000305175781, + "lat": 33.29999923706055 + }, + "ifa": "56700000-9cf0-22bd-b23e-46b96e40003a" + }, + "adspaceId": "130563103", + "publisherId": "1100042525" + } + }, + "want": { + "expectedJSON": { + "adspaceId": "130563103", + "publisherId": "1100042525" + } + } + }, + { + "name": "smartadserver_client_json", + "args": { + "adapterName": "smartadserver", + "requestJSON": { + "formatId": "84313", + "siteId": "317777", + "pageId": "1232599", + "domain": "http://ssb-us.smartadserver.com", + "networkId": "458" + } + }, + "want": { + "expectedJSON": { + "formatId": 84313, + "networkId": 458, + "pageId": 1232599, + "siteId": 317777 + } + } + }, + { + "name": "gumgum_client_json", + "args": { + "adapterName": "gumgum", + "requestJSON": { + "inSlot": "9", + "inScreen": "ggumtest", + "zone": "APAC1234" + } + }, + "want": { + "expectedJSON": { + "zone": "APAC1234" + } + } + }, + { + "name": "pangle_client_json", + "args": { + "adapterName": "pangle", + "requestJSON": { + "placementid": "912340000", + "appid": "5123400", + "token": "sample_token" + } + }, + "want": { + "expectedJSON": { + "appid": "5123400", + "placementid": "912340000", + "token": "sample_token" + } + } + }, + { + "name": "sonobi_client_json", + "args": { + "adapterName": "sonobi", + "requestJSON": { + "ad_unit": "/43743431/DMDemo", + "placement_id": "1a2b3c4d5e6f1a2b3c4d", + "hfa": "123" + } + }, + "want": { + "expectedJSON": { + "TagID": "/43743431/DMDemo" + } + } + }, + { + "name": "adf_client_json", + "args": { + "adapterName": "adf", + "requestJSON": { + "inv": 1234, + "priceType": "gross", + "mid": 12345, + "mname": "Leaderboard", + "adxDomain": "adx.adform.net" + } + }, + "want": { + "expectedJSON": { + "mid": 12345 + } + } + }, + { + "name": "criteo_client_json", + "args": { + "adapterName": "criteo", + "requestJSON": { + "zoneId": "1023914" + } + }, + "want": { + "expectedJSON": { + "zoneId": 1023914 + } + } + }, + { + "name": "apacdex_client_json", + "args": { + "adapterName": "apacdex", + "requestJSON": { + "siteId": "test123", + "floorPrice": 0.9333, + "geo": { + "lat": 17.98928, + "lon": 99.7741712, + "accuracy": 20 + } + } + }, + "want": { + "expectedJSON": { + "siteId": "test123", + "floorPrice": 0.9333, + "geo": { + "lat": 17.98928, + "lon": 99.7741712, + "accuracy": 20 + } + } + } + } +] \ No newline at end of file diff --git a/modules/pubmatic/openwrap/adapters/tests/s2s_bidders.json b/modules/pubmatic/openwrap/adapters/tests/s2s_bidders.json new file mode 100644 index 00000000000..183c8491319 --- /dev/null +++ b/modules/pubmatic/openwrap/adapters/tests/s2s_bidders.json @@ -0,0 +1,18 @@ +[ + { + "name": "visx_client_json", + "args" : { + "adapterName": "visx", + "requestJSON": { + "uid": 123, + "size": [160, 600] + } + }, + "want": { + "expectedJSON": { + "uid": 123, + "size": [160,600] + } + } + } +] \ No newline at end of file diff --git a/modules/pubmatic/openwrap/adapters/util.go b/modules/pubmatic/openwrap/adapters/util.go new file mode 100644 index 00000000000..20509072ea8 --- /dev/null +++ b/modules/pubmatic/openwrap/adapters/util.go @@ -0,0 +1,177 @@ +package adapters + +import ( + "bytes" + "encoding/json" + "fmt" + "math" + "os" + "strconv" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +const FloatValuePrecision = 2 + +func getKeywordStringForPartner(impExt *models.ImpExtension, partner string) string { + if impExt != nil && impExt.Bidder != nil { + bidder := impExt.Bidder[partner] + if nil != bidder && len(bidder.KeyWords) > 0 { + if byts, err := json.Marshal(bidder.KeyWords); err == nil { + return string(byts) + } + } + } + return "" +} + +func toFixed(num float64, precision int) float64 { + output := math.Pow(10, float64(precision)) + return float64(round(num*output)) / output +} + +func round(num float64) int { + return int(num + math.Copysign(0.5, num)) +} + +func trimComma(buf *bytes.Buffer) { + b := buf.Bytes() + if len(b) > 0 && b[len(b)-1] == ',' { + b[len(b)-1] = ' ' + } +} + +func isDirectoryExists(location string) bool { + if _, err := os.Stat(location); err == nil { + // path to schemaDirectory exists + return true + } + return false +} + +// ----------- datatype utilities ---------- +func getInt(val interface{}) (int, bool) { + if val == nil { + return 0, false + } + + var result int + switch v := val.(type) { + case int: + result = v + case string: + iVal, err := strconv.Atoi(v) + if err != nil { + return 0, false + } + result = iVal + case float64: + result = int(v) + case float32: + result = int(v) + default: + iVal, err := strconv.Atoi(fmt.Sprint(v)) + if err != nil { + return 0, false + } + result = iVal + } + return result, true +} + +func getFloat64(val interface{}) (float64, bool) { + if val == nil { + return 0, false + } + + var result float64 + switch v := val.(type) { + case float64: + result = v + case string: + fVal, err := strconv.ParseFloat(v, 64) + if err != nil { + return 0, false + } + result = fVal + case int: + result = float64(v) + default: + fVal, err := strconv.ParseFloat(fmt.Sprint(v), 64) + if err != nil { + return 0, false + } + result = fVal + } + return result, true +} + +func getString(val interface{}) (string, bool) { + if val == nil { + return "", false + } + + var result string + switch v := val.(type) { + case string: + result = v + case int: + result = strconv.Itoa(v) + case map[string]interface{}: + val, err := json.Marshal(v) + if err != nil { + return "", false + } + result = string(val) + default: + result = fmt.Sprint(val) + } + + return result, true +} + +func getBool(val interface{}) (bool, bool) { + if val == nil { + return false, false + } + + var result bool + switch v := val.(type) { + case bool: + result = v + case string: + bVal, err := strconv.ParseBool(v) + if err != nil { + return false, false + } + result = bVal + default: + bVal, err := strconv.ParseBool(fmt.Sprint(v)) + if err != nil { + return false, false + } + result = bVal + } + + return result, true +} + +func getIntArray(val interface{}) ([]int, bool) { + if val == nil { + return nil, false + } + + valArray, ok := val.([]interface{}) + if !ok { + return nil, false + } + + result := make([]int, 0) + for _, x := range valArray { + if val, ok := getInt(x); ok { + result = append(result, val) + } + } + + return result, true +} diff --git a/modules/pubmatic/openwrap/adapters/vastbidder.go b/modules/pubmatic/openwrap/adapters/vastbidder.go new file mode 100644 index 00000000000..f656df521f2 --- /dev/null +++ b/modules/pubmatic/openwrap/adapters/vastbidder.go @@ -0,0 +1,83 @@ +package adapters + +import ( + "encoding/json" + "strconv" + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func PrepareVASTBidderParamJSON(request *openrtb2.BidRequest, imp *openrtb2.Imp, + pubVASTTags models.PublisherVASTTags, + matchedSlotKeys []string, slotMap map[string]models.SlotMapping, + adpod *models.AdPod) json.RawMessage { + + if nil == imp.Video { + return nil + } + + bidderExt := openrtb_ext.ExtImpVASTBidder{} + bidderExt.Tags = make([]*openrtb_ext.ExtImpVASTBidderTag, len(matchedSlotKeys)) + var tagIndex int = 0 + for _, slotKey := range matchedSlotKeys { + vastTagID := getVASTTagID(slotKey) + if 0 == vastTagID { + continue + } + + vastTag, ok := pubVASTTags[vastTagID] + if false == ok { + continue + } + + slotMappingObj, ok := slotMap[strings.ToLower(slotKey)] + if !ok { + continue + } + + mapping := slotMappingObj.SlotMappings + + //adding mapping parameters as it is in ext.bidder + params := mapping + /* + params := make(map[string]interface{}) + // Copy from the original map of for slot key to the target map + for key, value := range mapping { + params[key] = value + } + */ + + //prepare bidder ext json here + bidderExt.Tags[tagIndex] = &openrtb_ext.ExtImpVASTBidderTag{ + //TagID: strconv.Itoa(vastTag.ID), + TagID: slotKey, + URL: vastTag.URL, + Duration: vastTag.Duration, + Price: vastTag.Price, + Params: params, + } + tagIndex++ + } + + if tagIndex > 0 { + //If any vast tags found then create impression ext for vast bidder. + bidderExt.Tags = bidderExt.Tags[:tagIndex] + bidParamBuf, _ := json.Marshal(bidderExt) + return bidParamBuf + } + return nil +} + +// getVASTTagID returns VASTTag ID details from slot key +func getVASTTagID(key string) int { + index := strings.LastIndex(key, "@") + if -1 == index { + return 0 + } + id, _ := strconv.Atoi(key[index+1:]) + return id +} diff --git a/modules/pubmatic/openwrap/adapterthrottle.go b/modules/pubmatic/openwrap/adapterthrottle.go new file mode 100644 index 00000000000..25dd6b699fe --- /dev/null +++ b/modules/pubmatic/openwrap/adapterthrottle.go @@ -0,0 +1,52 @@ +package openwrap + +import ( + "math/rand" + "strconv" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +// GetAdapterThrottleMap creates map of adapter and bool value which tells whether the adapter should be throtled or not +func GetAdapterThrottleMap(partnerConfigMap map[int]map[string]string) (map[string]struct{}, bool) { + adapterThrottleMap := make(map[string]struct{}) + allPartnersThrottledFlag := true + for _, partnerConfig := range partnerConfigMap { + if partnerConfig[models.SERVER_SIDE_FLAG] != "1" { + continue + } + if ThrottleAdapter(partnerConfig) { + adapterThrottleMap[partnerConfig[models.BidderCode]] = struct{}{} + } else if allPartnersThrottledFlag { + allPartnersThrottledFlag = false + } + } + + return adapterThrottleMap, allPartnersThrottledFlag +} + +// ThrottleAdapter this function returns bool value for whether a adapter should be throttled or not +func ThrottleAdapter(partnerConfig map[string]string) bool { + if partnerConfig[models.THROTTLE] == "100" || partnerConfig[models.THROTTLE] == "" { + return false + } + + if partnerConfig[models.THROTTLE] == "0" { + return true + } + + //else check throttle value based on random no + throttle, _ := strconv.ParseFloat(partnerConfig[models.THROTTLE], 64) + throttle = 100 - throttle + + randomNumberBelow100 := GetRandomNumberBelow100() + return !(float64(randomNumberBelow100) >= throttle) +} + +var GetRandomNumberBelow100 = func() int { + return rand.Intn(99) +} + +var GetRandomNumberIn1To100 = func() int { + return rand.Intn(100) + 1 +} diff --git a/modules/pubmatic/openwrap/adapterthrottle_test.go b/modules/pubmatic/openwrap/adapterthrottle_test.go new file mode 100644 index 00000000000..c3d449ede7a --- /dev/null +++ b/modules/pubmatic/openwrap/adapterthrottle_test.go @@ -0,0 +1,207 @@ +package openwrap + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/stretchr/testify/assert" +) + +func TestGetAdapterThrottleMap(t *testing.T) { + type args struct { + partnerConfigMap map[int]map[string]string + } + type want struct { + adapterThrottleMap map[string]struct{} + allPartnersThrottledFlag bool + } + tests := []struct { + name string + args args + want want + }{ + { + name: "All_partner_are_client_side_throttled", + args: args{ + partnerConfigMap: map[int]map[string]string{ + 0: { + models.THROTTLE: "0", + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.SERVER_SIDE_FLAG: "0", + }, + 1: { + models.THROTTLE: "0", + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.SERVER_SIDE_FLAG: "0", + }, + }, + }, + want: want{ + adapterThrottleMap: map[string]struct{}{}, + allPartnersThrottledFlag: true, + }, + }, + { + name: "one_partner_throttled_out_of_two", + args: args{ + partnerConfigMap: map[int]map[string]string{ + 0: { + models.THROTTLE: "0", + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.SERVER_SIDE_FLAG: "1", + }, + 1: { + models.THROTTLE: "100", + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + want: want{ + adapterThrottleMap: map[string]struct{}{ + "pubmatic": {}, + }, + allPartnersThrottledFlag: false, + }, + }, + { + name: "no_partner_throttled_out_of_two", + args: args{ + partnerConfigMap: map[int]map[string]string{ + 0: { + models.THROTTLE: "100", + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.SERVER_SIDE_FLAG: "1", + }, + 1: { + models.THROTTLE: "100", + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + want: want{ + adapterThrottleMap: map[string]struct{}{}, + allPartnersThrottledFlag: false, + }, + }, + { + name: "All_server_side_partner_throttled", + args: args{ + partnerConfigMap: map[int]map[string]string{ + 0: { + models.THROTTLE: "0", + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.SERVER_SIDE_FLAG: "1", + }, + 1: { + models.THROTTLE: "0", + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + want: want{ + adapterThrottleMap: map[string]struct{}{ + "pubmatic": {}, + "appnexus": {}, + }, + allPartnersThrottledFlag: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + adapterThrottleMap, allPartnersThrottledFlag := GetAdapterThrottleMap(tt.args.partnerConfigMap) + assert.Equal(t, tt.want.adapterThrottleMap, adapterThrottleMap) + assert.Equal(t, tt.want.allPartnersThrottledFlag, allPartnersThrottledFlag) + }) + } +} + +func TestThrottleAdapter(t *testing.T) { + type args struct { + partnerConfig map[string]string + val int + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "partner_throtlle_is_100", + args: args{ + partnerConfig: map[string]string{ + models.THROTTLE: "100", + }, + }, + want: false, + }, + { + name: "partner_throtlle_is_empty", + args: args{ + partnerConfig: map[string]string{ + models.THROTTLE: "", + }, + }, + want: false, + }, + { + name: "partner_throtlle_is_0", + args: args{ + partnerConfig: map[string]string{ + models.THROTTLE: "0", + }, + }, + want: true, + }, + { + name: "partner_throtlle_is_greater_than_0_and_less_than_100_and_random_number_generated_is_10", + args: args{ + partnerConfig: map[string]string{ + models.THROTTLE: "70", + }, + val: 10, + }, + want: true, + }, + { + name: "partner_throtlle_is_greater_than_0_and_less_than_100_and_random_number_generated_is_30", + args: args{ + partnerConfig: map[string]string{ + models.THROTTLE: "70", + }, + val: 30, + }, + want: false, + }, + { + name: "partner_throtlle_is_greater_than_0_and_less_than_100_and_random_number_generated_is_50", + args: args{ + partnerConfig: map[string]string{ + models.THROTTLE: "70", + }, + val: 50, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + GetRandomNumberBelow100 = func() int { + return tt.args.val + } + got := ThrottleAdapter(tt.args.partnerConfig) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/adunitconfig/app.go b/modules/pubmatic/openwrap/adunitconfig/app.go new file mode 100644 index 00000000000..8a55da0b48d --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/app.go @@ -0,0 +1,108 @@ +package adunitconfig + +import ( + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +func ReplaceAppObjectFromAdUnitConfig(rCtx models.RequestCtx, app *openrtb2.App) { + if app == nil { + return + } + + var adUnitCfg *adunitconfig.AdConfig + for _, impCtx := range rCtx.ImpBidCtx { + if impCtx.BannerAdUnitCtx.AppliedSlotAdUnitConfig != nil { + adUnitCfg = impCtx.BannerAdUnitCtx.AppliedSlotAdUnitConfig + break + } + if impCtx.VideoAdUnitCtx.AppliedSlotAdUnitConfig != nil { + adUnitCfg = impCtx.VideoAdUnitCtx.AppliedSlotAdUnitConfig + break + } + } + + if adUnitCfg == nil || adUnitCfg.App == nil { + return + } + + if app.ID == "" { + app.ID = adUnitCfg.App.ID + } + + if app.Name == "" { + app.Name = adUnitCfg.App.Name + } + + if app.Bundle == "" { + app.Bundle = adUnitCfg.App.Bundle + } + + if app.Domain == "" { + app.Domain = adUnitCfg.App.Domain + } + + if app.StoreURL == "" { + app.StoreURL = adUnitCfg.App.StoreURL + } + + if len(app.Cat) == 0 { + app.Cat = adUnitCfg.App.Cat + } + + if len(app.SectionCat) == 0 { + app.SectionCat = adUnitCfg.App.SectionCat + } + + if len(app.PageCat) == 0 { + app.PageCat = adUnitCfg.App.PageCat + } + + if app.Ver == "" { + app.Ver = adUnitCfg.App.Ver + } + + if app.PrivacyPolicy == 0 { + app.PrivacyPolicy = adUnitCfg.App.PrivacyPolicy + } + + if app.Paid == 0 { + app.Paid = adUnitCfg.App.Paid + } + + if app.Content == nil { + app.Content = adUnitCfg.App.Content + } + + if app.Keywords == "" { + app.Keywords = adUnitCfg.App.Keywords + } + + if app.Ext == nil { + app.Ext = adUnitCfg.App.Ext + } + + if adUnitCfg.App.Publisher != nil { + if app.Publisher == nil { + app.Publisher = &openrtb2.Publisher{} + } + + if app.Publisher.Name == "" { + app.Publisher.Name = adUnitCfg.App.Publisher.Name + } + + if len(app.Publisher.Cat) == 0 { + app.Publisher.Cat = adUnitCfg.App.Publisher.Cat + } + + if app.Publisher.Domain == "" { + app.Publisher.Domain = adUnitCfg.App.Publisher.Domain + } + + if app.Publisher.Ext == nil { + app.Publisher.Ext = adUnitCfg.App.Publisher.Ext + } + } + +} diff --git a/modules/pubmatic/openwrap/adunitconfig/banner.go b/modules/pubmatic/openwrap/adunitconfig/banner.go new file mode 100644 index 00000000000..a37c15624cf --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/banner.go @@ -0,0 +1,65 @@ +package adunitconfig + +import ( + "runtime/debug" + + "github.com/golang/glog" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +func UpdateBannerObjectWithAdunitConfig(rCtx models.RequestCtx, imp openrtb2.Imp, div string) (adUnitCtx models.AdUnitCtx) { + defer func() { + if r := recover(); r != nil { + glog.Error(string(debug.Stack())) + } + }() + + bannerAdUnitConfigEnabled := true + defer func() { + if imp.Banner != nil && !bannerAdUnitConfigEnabled { + rCtx.MetricsEngine.RecordImpDisabledViaConfigStats(models.ImpTypeBanner, rCtx.PubIDStr, rCtx.ProfileIDStr) + } + }() + + if rCtx.AdUnitConfig == nil || len(rCtx.AdUnitConfig.Config) == 0 { + return + } + + defaultAdUnitConfig, ok := rCtx.AdUnitConfig.Config[models.AdunitConfigDefaultKey] + if ok && defaultAdUnitConfig != nil { + adUnitCtx.UsingDefaultConfig = true + + if defaultAdUnitConfig.Banner != nil && defaultAdUnitConfig.Banner.Enabled != nil && !*defaultAdUnitConfig.Banner.Enabled { + bannerAdUnitConfigEnabled = false + adUnitCtx.AppliedSlotAdUnitConfig = &adunitconfig.AdConfig{Banner: &adunitconfig.Banner{Enabled: &bannerAdUnitConfigEnabled}} + return + } + } + + var height, width int64 + if imp.Banner != nil { + if imp.Banner.H != nil { + height = *imp.Banner.H + } + if imp.Banner.W != nil { + width = *imp.Banner.W + } + } + + adUnitCtx.SelectedSlotAdUnitConfig, adUnitCtx.MatchedSlot, adUnitCtx.IsRegex, adUnitCtx.MatchedRegex = selectSlot(rCtx, height, width, imp.TagID, div, rCtx.Source) + if adUnitCtx.SelectedSlotAdUnitConfig != nil && adUnitCtx.SelectedSlotAdUnitConfig.Banner != nil { + adUnitCtx.UsingDefaultConfig = false + + if adUnitCtx.SelectedSlotAdUnitConfig.Banner.Enabled != nil && !*adUnitCtx.SelectedSlotAdUnitConfig.Banner.Enabled { + bannerAdUnitConfigEnabled = false + adUnitCtx.AppliedSlotAdUnitConfig = &adunitconfig.AdConfig{Banner: &adunitconfig.Banner{Enabled: &bannerAdUnitConfigEnabled}} + return + } + } + + adUnitCtx.AppliedSlotAdUnitConfig = getFinalSlotAdUnitConfig(adUnitCtx.SelectedSlotAdUnitConfig, defaultAdUnitConfig) + + return +} diff --git a/modules/pubmatic/openwrap/adunitconfig/banner_test.go b/modules/pubmatic/openwrap/adunitconfig/banner_test.go new file mode 100644 index 00000000000..29b05f8cfcd --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/banner_test.go @@ -0,0 +1,373 @@ +package adunitconfig + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v19/openrtb2" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestUpdateBannerObjectWithAdunitConfig(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + + type args struct { + rCtx models.RequestCtx + imp openrtb2.Imp + div string + } + tests := []struct { + name string + args args + setup func() + wantAdUnitCtx models.AdUnitCtx + }{ + { + name: "AdunitConfig_is_nil", + args: args{ + rCtx: models.RequestCtx{ + MetricsEngine: mockEngine, + AdUnitConfig: nil, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](200), + ID: "123", + }, + }, + }, + wantAdUnitCtx: models.AdUnitCtx{}, + }, + { + name: "AdunitConfig_is_empty", + args: args{ + rCtx: models.RequestCtx{ + MetricsEngine: mockEngine, + AdUnitConfig: &adunitconfig.AdUnitConfig{}, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](200), + ID: "123", + }, + }, + }, + wantAdUnitCtx: models.AdUnitCtx{}, + }, + { + name: "request_imp_has_Banner_but_disabled_through_config_default", + args: args{ + rCtx: models.RequestCtx{ + MetricsEngine: mockEngine, + AdUnitConfig: &adunitconfig.AdUnitConfig{ + Config: map[string]*adunitconfig.AdConfig{ + "default": { + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](200), + ID: "123", + }, + }, + }, + setup: func() { + mockEngine.EXPECT().RecordImpDisabledViaConfigStats(models.ImpTypeBanner, "5890", "123").Times(1) + }, + wantAdUnitCtx: models.AdUnitCtx{ + UsingDefaultConfig: true, + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + { + name: "request_imp_has_Banner_but_disabled_through_config_for_particular_slot", + args: args{ + rCtx: models.RequestCtx{ + MetricsEngine: mockEngine, + AdUnitConfig: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Config: map[string]*adunitconfig.AdConfig{ + "default": { + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + }, + }, + "/12344/test_adunit": { + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + TagID: "/12344/Test_AdUnit", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](200), + ID: "123", + }, + }, + }, + setup: func() { + mockEngine.EXPECT().RecordImpDisabledViaConfigStats(models.ImpTypeBanner, "5890", "123").Times(1) + }, + wantAdUnitCtx: models.AdUnitCtx{ + UsingDefaultConfig: false, + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(false), + }, + }, + MatchedSlot: "/12344/Test_AdUnit", + IsRegex: false, + MatchedRegex: "", + SelectedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + { + name: "final_adunit_config_formed_using_both_default_and_slot._banner_selected_from_slot", + args: args{ + rCtx: models.RequestCtx{ + MetricsEngine: mockEngine, + AdUnitConfig: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Config: map[string]*adunitconfig.AdConfig{ + "default": { + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.BannerConfig{ + Banner: openrtb2.Banner{ + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](200), + ID: "123", + }, + }, + }, + }, + "/12344/test_adunit": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + }, + }, + }, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + TagID: "/12344/Test_AdUnit", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](200), + ID: "123", + }, + }, + }, + wantAdUnitCtx: models.AdUnitCtx{ + MatchedSlot: "/12344/Test_AdUnit", + IsRegex: false, + MatchedRegex: "", + SelectedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.BannerConfig{ + Banner: openrtb2.Banner{ + ID: "123", + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](200), + }, + }, + }, + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + }, + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.BannerConfig{ + Banner: openrtb2.Banner{ + ID: "123", + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](200), + }, + }, + }, + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + }, + UsingDefaultConfig: true, + AllowedConnectionTypes: nil, + }, + }, + { + name: "both_slot_and_default_config_are_nil", + args: args{ + rCtx: models.RequestCtx{ + AdUnitConfig: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Config: map[string]*adunitconfig.AdConfig{ + "default": nil, + "/12344/test_adunit": nil, + }, + }, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + TagID: "/12344/Test_AdUnit", + Banner: &openrtb2.Banner{ + ID: "123", + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](200), + }, + }, + }, + wantAdUnitCtx: models.AdUnitCtx{ + MatchedSlot: "/12344/Test_AdUnit", + IsRegex: false, + MatchedRegex: "", + SelectedSlotAdUnitConfig: nil, + AppliedSlotAdUnitConfig: nil, + UsingDefaultConfig: false, + AllowedConnectionTypes: nil, + }, + }, + { + name: "Banner_config_is_prsent_in_both_default_and_slot_preferance_is_given_to_slot_level", + args: args{ + rCtx: models.RequestCtx{ + MetricsEngine: mockEngine, + AdUnitConfig: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Config: map[string]*adunitconfig.AdConfig{ + "default": { + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.BannerConfig{ + Banner: openrtb2.Banner{ + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](200), + ID: "123", + }, + }, + }, + }, + "/12344/test_adunit": { + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.BannerConfig{ + Banner: openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](400), + ID: "123456", + }, + }, + }, + }, + }, + }, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + TagID: "/12344/Test_AdUnit", + Banner: &openrtb2.Banner{}, + }, + }, + wantAdUnitCtx: models.AdUnitCtx{ + MatchedSlot: "/12344/Test_AdUnit", + IsRegex: false, + MatchedRegex: "", + SelectedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.BannerConfig{ + Banner: openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](400), + ID: "123456", + }, + }, + }, + }, + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.BannerConfig{ + Banner: openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](400), + ID: "123456", + }, + }, + }, + }, + UsingDefaultConfig: false, + AllowedConnectionTypes: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + gotAdUnitCtx := UpdateBannerObjectWithAdunitConfig(tt.args.rCtx, tt.args.imp, tt.args.div) + assert.Equal(t, tt.wantAdUnitCtx, gotAdUnitCtx) + }) + } +} diff --git a/modules/pubmatic/openwrap/adunitconfig/common.go b/modules/pubmatic/openwrap/adunitconfig/common.go new file mode 100644 index 00000000000..aac08a205e8 --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/common.go @@ -0,0 +1,59 @@ +package adunitconfig + +import ( + "encoding/json" + "strings" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +func selectSlot(rCtx models.RequestCtx, h, w int64, tagid, div, source string) (slotAdUnitConfig *adunitconfig.AdConfig, slotName string, isRegex bool, matchedRegex string) { + slotName = models.GenerateSlotName(h, w, rCtx.AdUnitConfig.ConfigPattern, tagid, div, rCtx.Source) + + if slotAdUnitConfig, ok := rCtx.AdUnitConfig.Config[strings.ToLower(slotName)]; ok { + return slotAdUnitConfig, slotName, false, "" + } else if rCtx.AdUnitConfig.Regex { + if matchedRegex = getRegexMatch(rCtx, strings.ToLower(slotName)); matchedRegex != "" { + return rCtx.AdUnitConfig.Config[matchedRegex], slotName, true, matchedRegex + } + } + + return nil, "", false, "" +} + +/*GetClientConfigForMediaType function fetches the client config data from the ad unit config JSON for the given media type*/ +func GetClientConfigForMediaType(rctx models.RequestCtx, impID string, mediaType string) json.RawMessage { + if rctx.AdUnitConfig == nil || rctx.AdUnitConfig.Config == nil { + return nil + } + + impData, ok := rctx.ImpBidCtx[impID] + if !ok { + return nil + } + + if mediaType == models.AdunitConfigSlotBannerKey { + if impData.BannerAdUnitCtx.AppliedSlotAdUnitConfig != nil && + impData.BannerAdUnitCtx.AppliedSlotAdUnitConfig.Banner != nil && + impData.BannerAdUnitCtx.AppliedSlotAdUnitConfig.Banner.Config != nil { + if impData.BannerAdUnitCtx.AppliedSlotAdUnitConfig.Banner.Enabled != nil && + *impData.BannerAdUnitCtx.AppliedSlotAdUnitConfig.Banner.Enabled == false { + return nil + } + return impData.BannerAdUnitCtx.AppliedSlotAdUnitConfig.Banner.Config.ClientConfig + } + } else if mediaType == models.AdunitConfigSlotVideoKey { + if impData.VideoAdUnitCtx.AppliedSlotAdUnitConfig != nil && + impData.VideoAdUnitCtx.AppliedSlotAdUnitConfig.Video != nil && + impData.VideoAdUnitCtx.AppliedSlotAdUnitConfig.Video.Config != nil { + if impData.VideoAdUnitCtx.AppliedSlotAdUnitConfig.Video.Enabled != nil && + *impData.VideoAdUnitCtx.AppliedSlotAdUnitConfig.Video.Enabled == false { + return nil + } + return impData.VideoAdUnitCtx.AppliedSlotAdUnitConfig.Video.Config.ClientConfig + } + } + + return nil +} diff --git a/modules/pubmatic/openwrap/adunitconfig/common_test.go b/modules/pubmatic/openwrap/adunitconfig/common_test.go new file mode 100644 index 00000000000..e2e58380c2c --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/common_test.go @@ -0,0 +1,183 @@ +package adunitconfig + +import ( + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func getAdunitConfigWithRx() *adunitconfig.AdUnitConfig { + return &adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Regex: true, + Config: map[string]*adunitconfig.AdConfig{ + "default": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + }, + }, + "^/15671365/test_adunit[0-9]*$": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + SkipAfter: 16, + MaxDuration: 57, + Skip: ptrutil.ToPtr[int8](2), + SkipMin: 11, + MinDuration: 15, + MIMEs: []string{ + "video/mp4", + "video/x-flv", + "video/mp4", + "video/webm", + }, + }, + ConnectionType: []int{ + 1, + 2, + 6, + }, + }, + }, + }, + "/15671365/test_adunit1": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{}, + }, + }, + }, + } +} + +func TestSelectSlot(t *testing.T) { + type args struct { + rCtx models.RequestCtx + h int64 + w int64 + tagid string + div string + source string + } + type want struct { + slotAdUnitConfig *adunitconfig.AdConfig + slotName string + isRegex bool + matchedRegex string + } + tests := []struct { + name string + args args + want want + }{ + { + name: "Matching_Slot_config_when_regex_is_present_and_slotconfig_is_absent", + args: args{ + rCtx: models.RequestCtx{ + AdUnitConfig: func() *adunitconfig.AdUnitConfig { + auc := getAdunitConfigWithRx() + + // Temporary fix to make UT execution consistent. + // TODO: make getRegexMatch()'s loop consistent. + delete(auc.Config, "/15671365/test_adunit1") + return auc + }(), + }, + h: 300, + w: 200, + tagid: "/15671365/Test_AdUnit92349", + div: "Div1", + source: "test.com", + }, + want: want{ + slotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + SkipAfter: 16, + MaxDuration: 57, + Skip: ptrutil.ToPtr[int8](2), + SkipMin: 11, + MinDuration: 15, + MIMEs: []string{ + "video/mp4", + "video/x-flv", + "video/mp4", + "video/webm", + }, + }, + ConnectionType: []int{ + 1, + 2, + 6, + }, + }, + }, + }, + slotName: "/15671365/Test_AdUnit92349", + isRegex: true, + matchedRegex: "^/15671365/test_adunit[0-9]*$", + }, + }, + { + name: "Priority_to_Exact_Match_for_Slot_config_when_regex_is_also_present", + args: args{ + rCtx: models.RequestCtx{ + AdUnitConfig: getAdunitConfigWithRx(), + }, + h: 300, + w: 200, + tagid: "/15671365/Test_AdUnit1", + div: "Div1", + source: "test.com", + }, + want: want{ + slotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{}, + }, + }, + slotName: "/15671365/Test_AdUnit1", + isRegex: false, + matchedRegex: "", + }, + }, + { + name: "when_slot_name_does_not_match_slot_as_well_as_not_found_matched_regex", + args: args{ + rCtx: models.RequestCtx{ + AdUnitConfig: getAdunitConfigWithRx(), + }, + tagid: "/15627/Regex_Not_Registered", + }, + want: want{ + slotAdUnitConfig: nil, + slotName: "", + isRegex: false, + matchedRegex: "", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotSlotAdUnitConfig, gotSlotName, gotIsRegex, gotMatchedRegex := selectSlot(tt.args.rCtx, tt.args.h, tt.args.w, tt.args.tagid, tt.args.div, tt.args.source) + assert.Equal(t, tt.want.slotAdUnitConfig, gotSlotAdUnitConfig) + if gotSlotName != tt.want.slotName { + t.Errorf("selectSlot() gotSlotName = %v, want %v", gotSlotName, tt.want.slotName) + } + if gotIsRegex != tt.want.isRegex { + t.Errorf("selectSlot() gotIsRegex = %v, want %v", gotIsRegex, tt.want.isRegex) + } + if gotMatchedRegex != tt.want.matchedRegex { + t.Errorf("selectSlot() gotMatchedRegex = %v, want %v", gotMatchedRegex, tt.want.matchedRegex) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/adunitconfig/device.go b/modules/pubmatic/openwrap/adunitconfig/device.go new file mode 100644 index 00000000000..44b12a38d23 --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/device.go @@ -0,0 +1,34 @@ +package adunitconfig + +import ( + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +func ReplaceDeviceTypeFromAdUnitConfig(rCtx models.RequestCtx, device **openrtb2.Device) { + if *device == nil { + *device = &openrtb2.Device{} + } else if (*device).DeviceType != 0 { + return + } + + var adUnitCfg *adunitconfig.AdConfig + for _, impCtx := range rCtx.ImpBidCtx { + if impCtx.BannerAdUnitCtx.AppliedSlotAdUnitConfig != nil { + adUnitCfg = impCtx.BannerAdUnitCtx.AppliedSlotAdUnitConfig + break + } + if impCtx.VideoAdUnitCtx.AppliedSlotAdUnitConfig != nil { + adUnitCfg = impCtx.VideoAdUnitCtx.AppliedSlotAdUnitConfig + break + } + } + + if adUnitCfg == nil || adUnitCfg.Device == nil { + return + } + + (*device).DeviceType = adcom1.DeviceType(adUnitCfg.Device.DeviceType) +} diff --git a/modules/pubmatic/openwrap/adunitconfig/floors.go b/modules/pubmatic/openwrap/adunitconfig/floors.go new file mode 100644 index 00000000000..f1686b32b0e --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/floors.go @@ -0,0 +1,30 @@ +package adunitconfig + +import ( + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +func UpdateFloorsExtObjectFromAdUnitConfig(rCtx models.RequestCtx, requestExt *models.RequestExt) { + if requestExt.Prebid.Floors != nil { + return + } + + var adUnitCfg *adunitconfig.AdConfig + for _, impCtx := range rCtx.ImpBidCtx { + if impCtx.BannerAdUnitCtx.AppliedSlotAdUnitConfig != nil { + adUnitCfg = impCtx.BannerAdUnitCtx.AppliedSlotAdUnitConfig + break + } + if impCtx.VideoAdUnitCtx.AppliedSlotAdUnitConfig != nil { + adUnitCfg = impCtx.VideoAdUnitCtx.AppliedSlotAdUnitConfig + break + } + } + + if adUnitCfg == nil || adUnitCfg.Floors == nil { + return + } + + requestExt.Prebid.Floors = adUnitCfg.Floors +} diff --git a/modules/pubmatic/openwrap/adunitconfig/regex.go b/modules/pubmatic/openwrap/adunitconfig/regex.go new file mode 100644 index 00000000000..8a1b5978281 --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/regex.go @@ -0,0 +1,24 @@ +package adunitconfig + +import ( + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +func getRegexMatch(rctx models.RequestCtx, slotName string) string { + for expression := range rctx.AdUnitConfig.Config { + if expression != models.AdunitConfigDefaultKey { + //Populating and Validating + re, err := Compile(expression) + if err != nil { + // TODO: add debug messages + // errs = append(errs, err) + continue + } + + if re.MatchString(slotName) { + return expression + } + } + } + return "" +} diff --git a/modules/pubmatic/openwrap/adunitconfig/regex_cache.go b/modules/pubmatic/openwrap/adunitconfig/regex_cache.go new file mode 100644 index 00000000000..bae9be486ac --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/regex_cache.go @@ -0,0 +1,64 @@ +package adunitconfig + +import ( + "regexp" + "sync" +) + +// Check https://pkg.go.dev/github.com/umisama/go-regexpcache#section-readme for Regex-Caching +var ( + regexMapContainer regexMap +) + +// Compile parses a regular expression. +// This compatible with regexp.Compile but this uses a cache. +func Compile(str string) (*regexp.Regexp, error) { + return regexMapContainer.Get(str) +} + +// Match checks whether a textual regular expression matches a string. +// This compatible with regexp.MatchString but this uses a cache. +func MatchString(pattern string, s string) (matched bool, err error) { + re, err := Compile(pattern) + if err != nil { + return false, err + } + return re.MatchString(s), nil +} + +type regexMap struct { + regexps map[string]*regexp.Regexp + mu *sync.RWMutex +} + +func newContainer() regexMap { + return regexMap{ + regexps: make(map[string]*regexp.Regexp), + mu: &sync.RWMutex{}, + } +} + +func (s *regexMap) Get(str string) (*regexp.Regexp, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + re, ok := s.regexps[str] + if ok { + return re, nil + } + + var err error + + re, err = regexp.Compile(str) + + if err != nil { + return nil, err + } + s.regexps[str] = re + + return re, nil +} + +func init() { + regexMapContainer = newContainer() +} diff --git a/modules/pubmatic/openwrap/adunitconfig/regex_cache_test.go b/modules/pubmatic/openwrap/adunitconfig/regex_cache_test.go new file mode 100644 index 00000000000..891324cff03 --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/regex_cache_test.go @@ -0,0 +1,79 @@ +package adunitconfig + +import ( + "regexp" + "testing" +) + +func TestContainer(t *testing.T) { + cases := []struct { + name string + exp string + str string + expect_err bool + expect_match bool + }{ + {"test1", "^[hc]at", "cat", false, true}, + {"test2", "^[hc]at", "hat", false, true}, + {"test3", "^[hc]at", "hot", false, false}, + {"test4", `^^^[ddd!!\1\1\1\1`, "hot", true, true}, + } + + cont := newContainer() + for _, c := range cases { + re, err := cont.Get(c.exp) + if (err != nil) != c.expect_err { + t.Error("expect error, but got", err.Error()) + } + if c.expect_err { + continue + } + match := re.MatchString(c.str) + if match != c.expect_match { + t.Error("expect ", c.expect_match, ", but got ", match, "for test ", c.name) + } + } + +} + +func BenchmarkRegexpPackageCompile(b *testing.B) { + for i := 0; i < b.N; i++ { + re, _ := regexp.Compile(`^[hc]at`) + re.MatchString("cat") + } +} + +func BenchmarkRegexpCachePackageCompile(b *testing.B) { + for i := 0; i < b.N; i++ { + re, _ := Compile(`^[hc]at`) + re.MatchString("cat") + } +} + +func TestMatchString(t *testing.T) { + testCases := []struct { + pattern string + input string + expected bool + }{ + {`^[a-zA-Z]+$`, "abcdef", true}, + {`^[0-9]+$`, "12345", true}, + {`^[a-zA-Z]+$`, "12345", false}, + {`^[0-9]+$`, "abcdef", false}, + } + + for _, tc := range testCases { + t.Run(tc.pattern, func(t *testing.T) { + matched, err := MatchString(tc.pattern, tc.input) + + if err != nil { + t.Fatalf("Error while matching: %v", err) + } + + if matched != tc.expected { + t.Errorf("Expected match %v for input '%s' with pattern '%s', but got %v", + tc.expected, tc.input, tc.pattern, matched) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/adunitconfig/regex_test.go b/modules/pubmatic/openwrap/adunitconfig/regex_test.go new file mode 100644 index 00000000000..27dde8a2e17 --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/regex_test.go @@ -0,0 +1,109 @@ +package adunitconfig + +import ( + "testing" + + "github.com/magiconair/properties/assert" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/util/ptrutil" +) + +func TestGetRegexMatch(t *testing.T) { + type args struct { + rctx models.RequestCtx + slotName string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Matching_Slotname_with_regex_expression,_returing_valid_values", + args: args{ + rctx: models.RequestCtx{ + AdUnitConfig: &adunitconfig.AdUnitConfig{ + Regex: true, + Config: map[string]*adunitconfig.AdConfig{ + "^/15671365/MG_VideoAdUnit[0-9]*$": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + SkipAfter: 16, + MaxDuration: 57, + Skip: ptrutil.ToPtr[int8](2), + SkipMin: 11, + MinDuration: 15, + MIMEs: []string{ + "video/mp4", + "video/x-flv", + "video/mp4", + "video/webm", + }, + }, + }, + }, + }, + }, + }, + }, + slotName: "/15671365/MG_VideoAdUnit12349", + }, + want: "^/15671365/MG_VideoAdUnit[0-9]*$", + }, + { + name: "Slotname_and_regex_dont_match", + args: args{ + rctx: models.RequestCtx{ + AdUnitConfig: &adunitconfig.AdUnitConfig{ + Regex: true, + Config: map[string]*adunitconfig.AdConfig{ + "^/15671365/MG_VideoAdUnit[0-9]*$": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + SkipAfter: 16, + MaxDuration: 57, + Skip: ptrutil.ToPtr[int8](2), + SkipMin: 11, + MinDuration: 15, + MIMEs: []string{ + "video/mp4", + "video/x-flv", + "video/mp4", + "video/webm", + }, + }, + }, + }, + }, + }, + }, + }, + slotName: "/15627/Regex_Not_Registered", + }, + want: "", + }, + { + name: "Empty_AdunitConfig", + args: args{ + rctx: models.RequestCtx{ + AdUnitConfig: &adunitconfig.AdUnitConfig{ + Config: map[string]*adunitconfig.AdConfig{}, + }, + }, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getRegexMatch(tt.args.rctx, tt.args.slotName) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/adunitconfig/utils.go b/modules/pubmatic/openwrap/adunitconfig/utils.go new file mode 100644 index 00000000000..a5c581c9d61 --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/utils.go @@ -0,0 +1,103 @@ +package adunitconfig + +import ( + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/util/ptrutil" +) + +// TODO use this +func GetMatchedSlotName(rCtx models.RequestCtx, imp openrtb2.Imp, impExt models.ImpExtension) (slotAdUnitConfig *adunitconfig.AdConfig, isRegex bool) { + div := "" + height := imp.Video.H + width := imp.Video.W + tagID := imp.TagID + + if impExt.Wrapper != nil { + div = impExt.Wrapper.Div + } + + slotName := models.GenerateSlotName(height, width, rCtx.AdUnitConfig.ConfigPattern, tagID, div, rCtx.Source) + + var ok bool + slotAdUnitConfig, ok = rCtx.AdUnitConfig.Config[slotName] + if ok { + return + } + + // for slot, adUnitConfig := range rCtx.AdUnitConfig.Config { + + // } + + return +} + +func getDefaultAllowedConnectionTypes(adUnitConfigMap *adunitconfig.AdUnitConfig) []int { + if adUnitConfigMap == nil { + return nil + } + + if v, ok := adUnitConfigMap.Config[models.AdunitConfigDefaultKey]; ok && v.Video != nil && v.Video.Config != nil && len(v.Video.Config.CompanionType) != 0 { + return v.Video.Config.ConnectionType + } + + return nil +} + +func checkValuePresentInArray(intArray []int, value int) bool { + for _, eachVal := range intArray { + if eachVal == value { + return true + } + } + return false +} + +// update slotConfig with final AdUnit config to apply with +func getFinalSlotAdUnitConfig(slotConfig, defaultConfig *adunitconfig.AdConfig) *adunitconfig.AdConfig { + // nothing available + if slotConfig == nil && defaultConfig == nil { + return nil + } + + // only default available + if slotConfig == nil { + return defaultConfig + } + + // only slot available + if defaultConfig == nil { + return slotConfig + } + + // both available, merge both with priority to slot + + if (slotConfig.BidFloor == nil || *slotConfig.BidFloor == 0.0) && defaultConfig.BidFloor != nil { + slotConfig.BidFloor = defaultConfig.BidFloor + + slotConfig.BidFloorCur = ptrutil.ToPtr(models.USD) + if defaultConfig.BidFloorCur != nil { + slotConfig.BidFloorCur = defaultConfig.BidFloorCur + } + } + + //slotConfig has bidfloor and not have BidFloorCur set by default USD + if slotConfig.BidFloor != nil && *slotConfig.BidFloor > float64(0) && slotConfig.BidFloorCur == nil { + slotConfig.BidFloorCur = ptrutil.ToPtr(models.USD) + } + + if slotConfig.Banner == nil { + slotConfig.Banner = defaultConfig.Banner + } + + if slotConfig.Video == nil { + slotConfig.Video = defaultConfig.Video + } + + if slotConfig.Floors == nil { + slotConfig.Floors = defaultConfig.Floors + } + + return slotConfig +} diff --git a/modules/pubmatic/openwrap/adunitconfig/utils_test.go b/modules/pubmatic/openwrap/adunitconfig/utils_test.go new file mode 100644 index 00000000000..f3e8b512ac1 --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/utils_test.go @@ -0,0 +1,291 @@ +package adunitconfig + +import ( + "testing" + + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +var testSlotConfig = &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + Placement: 1, + Plcmt: 1, + MinDuration: 10, + MaxDuration: 20, + SkipMin: 13, + CompanionType: []adcom1.CompanionType{1, 2, 3}, + }, + ConnectionType: []int{ + 10, + 20, + 30, + }, + }, + }, +} + +var testDefaultconfig = &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + Placement: 1, + Plcmt: 1, + MinDuration: 10, + MaxDuration: 20, + SkipMin: 13, + CompanionType: []adcom1.CompanionType{1, 2, 3}, + }, + ConnectionType: []int{ + 10, + 20, + 30, + }, + }, + }, + Banner: &adunitconfig.Banner{ + Config: &adunitconfig.BannerConfig{ + Banner: openrtb2.Banner{ + ID: "123", + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](200), + }, + }, + }, + Floors: &openrtb_ext.PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "USD", + Enabled: ptrutil.ToPtr(true), + }, +} + +func TestGetDefaultAllowedConnectionTypes(t *testing.T) { + type args struct { + adUnitConfigMap *adunitconfig.AdUnitConfig + } + tests := []struct { + name string + args args + want []int + }{ + { + name: "adunitConfigMap_is_nil", + args: args{ + adUnitConfigMap: nil, + }, + want: nil, + }, + { + name: "adunitConfigMap_contian_non_empty_CompanionType", + args: args{ + adUnitConfigMap: &adunitconfig.AdUnitConfig{ + Config: map[string]*adunitconfig.AdConfig{ + models.AdunitConfigDefaultKey: { + Video: testSlotConfig.Video, + }, + }, + }, + }, + want: []int{10, 20, 30}, + }, + { + name: "adunitConfigMap_conatian_empty_CompanionType", + args: args{ + adUnitConfigMap: &adunitconfig.AdUnitConfig{ + Config: map[string]*adunitconfig.AdConfig{ + models.AdunitConfigDefaultKey: { + Video: &adunitconfig.Video{ + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + CompanionType: []adcom1.CompanionType{}, + }, + ConnectionType: []int{ + 10, + 20, + 30, + }, + }, + }, + }, + }, + }, + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getDefaultAllowedConnectionTypes(tt.args.adUnitConfigMap) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetFinalSlotAdUnitConfig(t *testing.T) { + type args struct { + slotConfig *adunitconfig.AdConfig + defaultConfig *adunitconfig.AdConfig + } + tests := []struct { + name string + args args + want *adunitconfig.AdConfig + }{ + { + name: "both_slotConfig_and_defaultConfig_are_nil", + args: args{ + slotConfig: nil, + defaultConfig: nil, + }, + want: nil, + }, + { + name: "slotConfig_is_nil", + args: args{ + slotConfig: nil, + defaultConfig: testDefaultconfig, + }, + want: testDefaultconfig, + }, + { + name: "defaultconfig_is_nil", + args: args{ + slotConfig: testSlotConfig, + defaultConfig: nil, + }, + want: testSlotConfig, + }, + { + name: "both_avilable_merge_priority_to_slot", + args: args{ + defaultConfig: testDefaultconfig, + slotConfig: testSlotConfig, + }, + want: &adunitconfig.AdConfig{ + Video: testSlotConfig.Video, + Banner: testDefaultconfig.Banner, + Floors: testDefaultconfig.Floors, + }, + }, + { + name: "Video_and_banner_is_not_avilable_in_slot_update_from_default", + args: args{ + slotConfig: &adunitconfig.AdConfig{ + Floors: &openrtb_ext.PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "USD", + Enabled: ptrutil.ToPtr(true), + }, + }, + defaultConfig: testDefaultconfig, + }, + want: testDefaultconfig, + }, + { + name: "Bidfloor_is_absent_in_slot,_present_in_default,_and_default_lacks_BidFloorCur", + args: args{ + slotConfig: &adunitconfig.AdConfig{ + BidFloor: nil, + }, + defaultConfig: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](4), + }, + }, + want: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](4), + BidFloorCur: ptrutil.ToPtr(models.USD), + }, + }, + { + name: "Bidfloor_is_absent_in_slot,_present_in_default,_and_default_also_have_BidFloorCur", + args: args{ + slotConfig: &adunitconfig.AdConfig{ + BidFloor: nil, + }, + defaultConfig: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](4), + BidFloorCur: ptrutil.ToPtr("INR"), + }, + }, + want: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](4), + BidFloorCur: ptrutil.ToPtr("INR"), + }, + }, + { + name: "Bidfloor_is_present_in_slot_but_has_zero_value_and_default_have_Bidfloor_and_BidFloorCur", + args: args{ + slotConfig: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](0.0), + BidFloorCur: ptrutil.ToPtr("INR"), + }, + defaultConfig: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](4.0), + BidFloorCur: ptrutil.ToPtr("EUR"), + }, + }, + want: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](4.0), + BidFloorCur: ptrutil.ToPtr("EUR"), + }, + }, + { + name: "Bid_Floor_from_slot_config_having_only_currency._default_gets_selected", + args: args{ + slotConfig: &adunitconfig.AdConfig{ + BidFloorCur: ptrutil.ToPtr("INR"), + }, + defaultConfig: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](10.0), + BidFloorCur: ptrutil.ToPtr("EUR"), + }, + }, + want: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](10.0), + BidFloorCur: ptrutil.ToPtr("EUR"), + }, + }, + { + name: "Bid_Floor,_No_bidfloorCur_in-default_config,_floor_value_from_default_gets_selected_and_default_currency_USD_gets_set", + args: args{ + slotConfig: &adunitconfig.AdConfig{}, + defaultConfig: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](10.0), + }, + }, + want: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](10.0), + BidFloorCur: ptrutil.ToPtr("USD"), + }, + }, + { + name: "slotConfig_has_bidfloor_but_not_have_BidFloorCur_set_by_default_USD", + args: args{ + slotConfig: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](4.0), + }, + defaultConfig: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](5.0), + BidFloorCur: ptrutil.ToPtr("EUR"), + }, + }, + want: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr[float64](4.0), + BidFloorCur: ptrutil.ToPtr("USD"), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getFinalSlotAdUnitConfig(tt.args.slotConfig, tt.args.defaultConfig) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/adunitconfig/video.go b/modules/pubmatic/openwrap/adunitconfig/video.go new file mode 100644 index 00000000000..d75bdcfddf9 --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/video.go @@ -0,0 +1,87 @@ +package adunitconfig + +import ( + "runtime/debug" + + "github.com/golang/glog" + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +func UpdateVideoObjectWithAdunitConfig(rCtx models.RequestCtx, imp openrtb2.Imp, div string, connectionType *adcom1.ConnectionType) (adUnitCtx models.AdUnitCtx) { + defer func() { + if r := recover(); r != nil { + glog.Error(string(debug.Stack())) + } + }() + + videoAdUnitConfigEnabled := true + defer func() { + if imp.Video != nil && !videoAdUnitConfigEnabled { + rCtx.MetricsEngine.RecordImpDisabledViaConfigStats(models.ImpTypeVideo, rCtx.PubIDStr, rCtx.ProfileIDStr) + } + }() + + if rCtx.AdUnitConfig == nil || len(rCtx.AdUnitConfig.Config) == 0 { + return + } + + defaultAdUnitConfig, ok := rCtx.AdUnitConfig.Config[models.AdunitConfigDefaultKey] + if ok && defaultAdUnitConfig != nil { + adUnitCtx.UsingDefaultConfig = true + + if defaultAdUnitConfig.Video != nil && defaultAdUnitConfig.Video.Enabled != nil && !*defaultAdUnitConfig.Video.Enabled { + videoAdUnitConfigEnabled = false + adUnitCtx.AppliedSlotAdUnitConfig = &adunitconfig.AdConfig{Video: &adunitconfig.Video{Enabled: &videoAdUnitConfigEnabled}} + return + } + } + + var height, width int64 + if imp.Video != nil { + height = imp.Video.H + width = imp.Video.W + } + + adUnitCtx.SelectedSlotAdUnitConfig, adUnitCtx.MatchedSlot, adUnitCtx.IsRegex, adUnitCtx.MatchedRegex = selectSlot(rCtx, height, width, imp.TagID, div, rCtx.Source) + if adUnitCtx.SelectedSlotAdUnitConfig != nil && adUnitCtx.SelectedSlotAdUnitConfig.Video != nil { + adUnitCtx.UsingDefaultConfig = false + if adUnitCtx.SelectedSlotAdUnitConfig.Video.Enabled != nil && !*adUnitCtx.SelectedSlotAdUnitConfig.Video.Enabled { + videoAdUnitConfigEnabled = false + adUnitCtx.AppliedSlotAdUnitConfig = &adunitconfig.AdConfig{Video: &adunitconfig.Video{Enabled: &videoAdUnitConfigEnabled}} + return + } + } + + adUnitCtx.AppliedSlotAdUnitConfig = getFinalSlotAdUnitConfig(adUnitCtx.SelectedSlotAdUnitConfig, defaultAdUnitConfig) + if adUnitCtx.AppliedSlotAdUnitConfig == nil { + return + } + + adUnitCtx.AllowedConnectionTypes = getDefaultAllowedConnectionTypes(rCtx.AdUnitConfig) + + // updateAllowedConnectionTypes := !adUnitCtx.UsingDefaultConfig + // if adUnitCtx.AppliedSlotAdUnitConfig != nil && adUnitCtx.AppliedSlotAdUnitConfig.Video != nil && + // adUnitCtx.AppliedSlotAdUnitConfig.Video.Config != nil && len(adUnitCtx.AppliedSlotAdUnitConfig.Video.Config.ConnectionType) != 0 { + // updateAllowedConnectionTypes = updateAllowedConnectionTypes && true + // } + + // // disable video if connection type is not present in allowed connection types from config + // if connectionType != nil { + // //check connection type in slot config + // if updateAllowedConnectionTypes { + // adUnitCtx.AllowedConnectionTypes = configObjInVideoConfig.ConnectionType + // } + + // if allowedConnectionTypes != nil && !checkValuePresentInArray(allowedConnectionTypes, int(*connectionType)) { + // f := false + // adUnitCtx.AppliedSlotAdUnitConfig = &adunitconfig.AdConfig{Video: &adunitconfig.Video{Enabled: &f}} + // rCtx.MetricsEngine.RecordVideoImpDisabledViaConnTypeStats(rCtx.PubIDStr,rCtx.ProfIDStr) + // return + // } + // } + + return +} diff --git a/modules/pubmatic/openwrap/adunitconfig/video_test.go b/modules/pubmatic/openwrap/adunitconfig/video_test.go new file mode 100644 index 00000000000..fe3207fc7da --- /dev/null +++ b/modules/pubmatic/openwrap/adunitconfig/video_test.go @@ -0,0 +1,434 @@ +package adunitconfig + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestUpdateVideoObjectWithAdunitConfig(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + + type args struct { + rCtx models.RequestCtx + imp openrtb2.Imp + div string + connectionType *adcom1.ConnectionType + } + tests := []struct { + name string + args args + setup func() + wantAdUnitCtx models.AdUnitCtx + }{ + { + name: "AdunitConfig_is_nil", + args: args{ + rCtx: models.RequestCtx{ + MetricsEngine: mockEngine, + AdUnitConfig: nil, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + Video: &openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + wantAdUnitCtx: models.AdUnitCtx{}, + }, + { + name: "AdunitConfig_is_empty", + args: args{ + rCtx: models.RequestCtx{ + MetricsEngine: mockEngine, + AdUnitConfig: &adunitconfig.AdUnitConfig{}, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + Video: &openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + wantAdUnitCtx: models.AdUnitCtx{}, + }, + { + name: "request_imp_has_Video_but_disabled_through_config_default", + args: args{ + rCtx: models.RequestCtx{ + MetricsEngine: mockEngine, + AdUnitConfig: &adunitconfig.AdUnitConfig{ + Config: map[string]*adunitconfig.AdConfig{ + "default": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + Video: &openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + setup: func() { + mockEngine.EXPECT().RecordImpDisabledViaConfigStats(models.ImpTypeVideo, "5890", "123").Times(1) + }, + wantAdUnitCtx: models.AdUnitCtx{ + UsingDefaultConfig: true, + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + { + name: "request_imp_has_Video_but_disabled_through_config_for_particular_slot", + args: args{ + rCtx: models.RequestCtx{ + MetricsEngine: mockEngine, + AdUnitConfig: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Config: map[string]*adunitconfig.AdConfig{ + "default": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + }, + }, + "/12344/test_adunit": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + TagID: "/12344/Test_AdUnit", + Video: &openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + setup: func() { + mockEngine.EXPECT().RecordImpDisabledViaConfigStats(models.ImpTypeVideo, "5890", "123").Times(1) + }, + wantAdUnitCtx: models.AdUnitCtx{ + UsingDefaultConfig: false, + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(false), + }, + }, + MatchedSlot: "/12344/Test_AdUnit", + IsRegex: false, + MatchedRegex: "", + SelectedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + { + name: "final_adunit_config_formed_using_both_default_and_slot._video_selected_from_slot", + args: args{ + rCtx: models.RequestCtx{ + MetricsEngine: mockEngine, + AdUnitConfig: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Config: map[string]*adunitconfig.AdConfig{ + "default": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + }, + "/12344/test_adunit": { + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.BannerConfig{ + Banner: openrtb2.Banner{ + ID: "123", + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](100), + }, + }, + }, + }, + }, + }, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + TagID: "/12344/Test_AdUnit", + Video: &openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + wantAdUnitCtx: models.AdUnitCtx{ + MatchedSlot: "/12344/Test_AdUnit", + IsRegex: false, + MatchedRegex: "", + SelectedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.BannerConfig{ + Banner: openrtb2.Banner{ + ID: "123", + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](100), + }, + }, + }, + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + }, + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.BannerConfig{ + Banner: openrtb2.Banner{ + ID: "123", + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](100), + }, + }, + }, + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + }, + UsingDefaultConfig: true, + AllowedConnectionTypes: nil, + }, + }, + { + name: "both_slot_and_default_config_are_nil", + args: args{ + rCtx: models.RequestCtx{ + AdUnitConfig: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Config: map[string]*adunitconfig.AdConfig{ + "default": nil, + "/12344/test_adunit": nil, + }, + }, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + TagID: "/12344/Test_AdUnit", + Video: &openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + wantAdUnitCtx: models.AdUnitCtx{ + MatchedSlot: "/12344/Test_AdUnit", + IsRegex: false, + MatchedRegex: "", + SelectedSlotAdUnitConfig: nil, + AppliedSlotAdUnitConfig: nil, + UsingDefaultConfig: false, + AllowedConnectionTypes: nil, + }, + }, + { + name: "AllowedConnectionTypes_updated_from_default", + args: args{ + rCtx: models.RequestCtx{ + AdUnitConfig: &adunitconfig.AdUnitConfig{ + ConfigPattern: "", + Config: map[string]*adunitconfig.AdConfig{ + "default": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + CompanionType: []adcom1.CompanionType{1, 2, 3}, + }, + ConnectionType: []int{10, 20, 30}, + }, + }, + }, + }, + }, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + TagID: "/12344/Test_AdUnit", + Video: &openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + wantAdUnitCtx: models.AdUnitCtx{ + MatchedSlot: "", + IsRegex: false, + MatchedRegex: "", + SelectedSlotAdUnitConfig: nil, + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + CompanionType: []adcom1.CompanionType{1, 2, 3}, + }, + ConnectionType: []int{10, 20, 30}, + }, + }, + }, + UsingDefaultConfig: true, + AllowedConnectionTypes: []int{10, 20, 30}, + }, + }, + { + name: "Video_config_is_prsent_in_both_default_and_slot_preferance_is_given_to_slot_level", + args: args{ + rCtx: models.RequestCtx{ + MetricsEngine: mockEngine, + AdUnitConfig: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Config: map[string]*adunitconfig.AdConfig{ + "default": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + }, + "/12344/test_adunit": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + MIMEs: []string{"test"}, + Plcmt: 4, + MinDuration: 4, + MaxDuration: 20, + }, + }, + }, + }, + }, + }, + PubIDStr: "5890", + ProfileIDStr: "123", + }, + imp: openrtb2.Imp{ + TagID: "/12344/Test_AdUnit", + Video: &openrtb2.Video{ + Plcmt: 2, + MinDuration: 2, + MaxDuration: 10, + }, + }, + }, + wantAdUnitCtx: models.AdUnitCtx{ + MatchedSlot: "/12344/Test_AdUnit", + IsRegex: false, + MatchedRegex: "", + SelectedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + MIMEs: []string{"test"}, + Plcmt: 4, + MinDuration: 4, + MaxDuration: 20, + }, + }, + }, + }, + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + MIMEs: []string{"test"}, + Plcmt: 4, + MinDuration: 4, + MaxDuration: 20, + }, + }, + }, + }, + UsingDefaultConfig: false, + AllowedConnectionTypes: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + gotAdUnitCtx := UpdateVideoObjectWithAdunitConfig(tt.args.rCtx, tt.args.imp, tt.args.div, tt.args.connectionType) + assert.Equal(t, tt.wantAdUnitCtx, gotAdUnitCtx) + }) + } +} diff --git a/modules/pubmatic/openwrap/allprocessedbidresponsehook.go b/modules/pubmatic/openwrap/allprocessedbidresponsehook.go new file mode 100644 index 00000000000..764ba2d76e1 --- /dev/null +++ b/modules/pubmatic/openwrap/allprocessedbidresponsehook.go @@ -0,0 +1,43 @@ +package openwrap + +import ( + "context" + + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/utils" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// handleAllProcessedBidResponsesHook will create unique id for each bid in bid Response. This hook is introduced +// because bidresponse should be updated in mutations and we need modified bidID at the start of auction response hook. +func (m OpenWrap) handleAllProcessedBidResponsesHook( + ctx context.Context, + moduleCtx hookstage.ModuleInvocationContext, + payload hookstage.AllProcessedBidResponsesPayload, +) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) { + result := hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload]{ + ChangeSet: hookstage.ChangeSet[hookstage.AllProcessedBidResponsesPayload]{}, + } + + // absence of rctx at this hook means the first hook failed!. Do nothing + if len(moduleCtx.ModuleContext) == 0 { + result.DebugMessages = append(result.DebugMessages, "error: module-ctx not found in handleAllProcessedBidResponsesHook()") + return result, nil + } + + result.ChangeSet.AddMutation(func(apbrp hookstage.AllProcessedBidResponsesPayload) (hookstage.AllProcessedBidResponsesPayload, error) { + updateBidIds(apbrp.Responses) + return apbrp, nil + }, hookstage.MutationUpdate, "update-bid-id") + + return result, nil +} + +func updateBidIds(bidderResponses map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) { + for _, seatBid := range bidderResponses { + for i := range seatBid.Bids { + seatBid.Bids[i].Bid.ID = utils.SetUniqueBidID(seatBid.Bids[i].Bid.ID, seatBid.Bids[i].GeneratedBidID) + } + } +} diff --git a/modules/pubmatic/openwrap/allprocessedbidresponsehook_test.go b/modules/pubmatic/openwrap/allprocessedbidresponsehook_test.go new file mode 100644 index 00000000000..28dc988034f --- /dev/null +++ b/modules/pubmatic/openwrap/allprocessedbidresponsehook_test.go @@ -0,0 +1,69 @@ +package openwrap + +import ( + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestUpdateBidIds(t *testing.T) { + type args struct { + bidderResponses map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid + } + tests := []struct { + name string + args args + want map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid + }{ + { + name: "All bidIds are updated", + args: args{ + bidderResponses: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid-1", + }, + GeneratedBidID: "gen-1", + }, + { + Bid: &openrtb2.Bid{ + ID: "bid-2", + }, + GeneratedBidID: "gen-2", + }, + }, + }, + }, + }, + want: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid-1::gen-1", + }, + GeneratedBidID: "gen-1", + }, + { + Bid: &openrtb2.Bid{ + ID: "bid-2::gen-2", + }, + GeneratedBidID: "gen-2", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + updateBidIds(tt.args.bidderResponses) + assert.Equal(t, tt.want, tt.args.bidderResponses, "Bid Id should be equal") + }) + } +} diff --git a/modules/pubmatic/openwrap/auctionresponsehook.go b/modules/pubmatic/openwrap/auctionresponsehook.go new file mode 100644 index 00000000000..8c99cfc9a47 --- /dev/null +++ b/modules/pubmatic/openwrap/auctionresponsehook.go @@ -0,0 +1,424 @@ +package openwrap + +import ( + "context" + "encoding/json" + "strconv" + "time" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adunitconfig" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/tracker" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/utils" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func (m OpenWrap) handleAuctionResponseHook( + ctx context.Context, + moduleCtx hookstage.ModuleInvocationContext, + payload hookstage.AuctionResponsePayload, +) (hookstage.HookResult[hookstage.AuctionResponsePayload], error) { + result := hookstage.HookResult[hookstage.AuctionResponsePayload]{} + result.ChangeSet = hookstage.ChangeSet[hookstage.AuctionResponsePayload]{} + + // absence of rctx at this hook means the first hook failed!. Do nothing + if len(moduleCtx.ModuleContext) == 0 { + result.DebugMessages = append(result.DebugMessages, "error: module-ctx not found in handleAuctionResponseHook()") + return result, nil + } + rctx, ok := moduleCtx.ModuleContext["rctx"].(models.RequestCtx) + if !ok { + result.DebugMessages = append(result.DebugMessages, "error: request-ctx not found in handleAuctionResponseHook()") + return result, nil + } + + //SSHB request should not execute module + if rctx.Sshb == "1" || rctx.Endpoint == models.EndpointHybrid { + return result, nil + } + + defer func() { + moduleCtx.ModuleContext["rctx"] = rctx + m.metricEngine.RecordPublisherResponseTimeStats(rctx.PubIDStr, int(time.Since(time.Unix(rctx.StartTime, 0)).Milliseconds())) + }() + + // cache rctx for analytics + result.AnalyticsTags = hookanalytics.Analytics{ + Activities: []hookanalytics.Activity{ + { + Name: "openwrap_request_ctx", + Results: []hookanalytics.Result{ + { + Values: map[string]interface{}{ + "request-ctx": &rctx, + }, + }, + }, + }, + }, + } + + // if payload.BidResponse.NBR != nil { + // return result, nil + // } + + anyDealTierSatisfyingBid := false + winningBids := make(map[string]models.OwBid, 0) + for _, seatBid := range payload.BidResponse.SeatBid { + for _, bid := range seatBid.Bid { + + m.metricEngine.RecordPlatformPublisherPartnerResponseStats(rctx.Platform, rctx.PubIDStr, seatBid.Seat) + + impCtx, ok := rctx.ImpBidCtx[bid.ImpID] + if !ok { + result.Errors = append(result.Errors, "invalid impCtx.ID for bid"+bid.ImpID) + continue + } + + partnerID := 0 + if bidderMeta, ok := impCtx.Bidders[seatBid.Seat]; ok { + partnerID = bidderMeta.PartnerID + } + + var eg, en float64 + bidExt := &models.BidExt{} + + if len(bid.Ext) != 0 { + err := json.Unmarshal(bid.Ext, bidExt) + if err != nil { + result.Errors = append(result.Errors, "failed to unmarshal bid.ext for "+bid.ID) + // continue + } + } + + // NYC_TODO: fix this in PBS-Core or ExecuteAllProcessedBidResponsesStage + if bidExt.Prebid != nil && bidExt.Prebid.Video != nil && bidExt.Prebid.Video.Duration == 0 && + bidExt.Prebid.Video.PrimaryCategory == "" && bidExt.Prebid.Video.VASTTagID == "" { + bidExt.Prebid.Video = nil + } + + if v, ok := rctx.PartnerConfigMap[models.VersionLevelConfigID]["refreshInterval"]; ok { + n, err := strconv.Atoi(v) + if err == nil { + bidExt.RefreshInterval = n + } + } + + if bidExt.Prebid != nil { + bidExt.CreativeType = string(bidExt.Prebid.Type) + } + if bidExt.CreativeType == "" { + bidExt.CreativeType = models.GetCreativeType(&bid, bidExt, &impCtx) + } + + // set response netecpm and logger/tracker en + revShare := models.GetRevenueShare(rctx.PartnerConfigMap[partnerID]) + bidExt.NetECPM = models.GetNetEcpm(bid.Price, revShare) + eg = bid.Price + en = bidExt.NetECPM + if payload.BidResponse.Cur != "USD" { + eg = bidExt.OriginalBidCPMUSD + en = models.GetNetEcpm(bidExt.OriginalBidCPMUSD, revShare) + bidExt.OriginalBidCPMUSD = 0 + } + + if impCtx.Video != nil && impCtx.Type == "video" && bidExt.CreativeType == "video" { + if bidExt.Video == nil { + bidExt.Video = &models.ExtBidVideo{} + } + if impCtx.Video.MaxDuration != 0 { + bidExt.Video.MaxDuration = impCtx.Video.MaxDuration + } + if impCtx.Video.MinDuration != 0 { + bidExt.Video.MinDuration = impCtx.Video.MinDuration + } + if impCtx.Video.Skip != nil { + bidExt.Video.Skip = impCtx.Video.Skip + } + if impCtx.Video.SkipAfter != 0 { + bidExt.Video.SkipAfter = impCtx.Video.SkipAfter + } + if impCtx.Video.SkipMin != 0 { + bidExt.Video.SkipMin = impCtx.Video.SkipMin + } + bidExt.Video.BAttr = impCtx.Video.BAttr + bidExt.Video.PlaybackMethod = impCtx.Video.PlaybackMethod + if rctx.ClientConfigFlag == 1 { + bidExt.Video.ClientConfig = adunitconfig.GetClientConfigForMediaType(rctx, bid.ImpID, "video") + } + } else if impCtx.Banner && bidExt.CreativeType == "banner" && rctx.ClientConfigFlag == 1 { + cc := adunitconfig.GetClientConfigForMediaType(rctx, bid.ImpID, "banner") + if len(cc) != 0 { + if bidExt.Banner == nil { + bidExt.Banner = &models.ExtBidBanner{} + } + bidExt.Banner.ClientConfig = cc + } + } + + bidDealTierSatisfied := false + if bidExt.Prebid != nil { + bidDealTierSatisfied = bidExt.Prebid.DealTierSatisfied + if bidDealTierSatisfied { + anyDealTierSatisfyingBid = true // found at least one bid which satisfies dealTier + } + } + + owbid := models.OwBid{ + ID: bid.ID, + NetEcpm: bidExt.NetECPM, + BidDealTierSatisfied: bidDealTierSatisfied, + } + wbid, oldWinBidFound := winningBids[bid.ImpID] + if !oldWinBidFound || isNewWinningBid(&owbid, &wbid, rctx.SupportDeals) { + winningBids[bid.ImpID] = owbid + } + + // update NonBr codes for current bid + if owbid.Nbr != nil { + bidExt.Nbr = owbid.Nbr + } + + // if current bid is winner then update NonBr code for earlier winning bid + if winningBids[bid.ImpID].ID == owbid.ID && oldWinBidFound { + winBidCtx := rctx.ImpBidCtx[bid.ImpID].BidCtx[wbid.ID] + winBidCtx.BidExt.Nbr = wbid.Nbr + rctx.ImpBidCtx[bid.ImpID].BidCtx[wbid.ID] = winBidCtx + } + + // cache for bid details for logger and tracker + if impCtx.BidCtx == nil { + impCtx.BidCtx = make(map[string]models.BidCtx) + } + impCtx.BidCtx[bid.ID] = models.BidCtx{ + BidExt: *bidExt, + EG: eg, + EN: en, + } + rctx.ImpBidCtx[bid.ImpID] = impCtx + } + } + + rctx.WinningBids = winningBids + if len(winningBids) == 0 { + m.metricEngine.RecordNobidErrPrebidServerResponse(rctx.PubIDStr) + } + + /* + At this point of time, + 1. For price-based auction (request with supportDeals = false), + all rejected bids will have NonBR code as LossLostToHigherBid which is expected. + 2. For request with supportDeals = true : + 2.1) If all bids are non-deal-bids (bidExt.Prebid.DealTierSatisfied = false) + then NonBR code for them will be LossLostToHigherBid which is expected. + 2.2) If one of the bid is deal-bid (bidExt.Prebid.DealTierSatisfied = true) + expectation: + all rejected non-deal bids should have NonBR code as LossLostToDealBid + all rejected deal-bids should have NonBR code as LossLostToHigherBid + addLostToDealBidNonBRCode function will make sure that above expectation are met. + */ + if anyDealTierSatisfyingBid { + addLostToDealBidNonBRCode(&rctx) + } + + droppedBids, warnings := addPWTTargetingForBid(rctx, payload.BidResponse) + if len(droppedBids) != 0 { + rctx.DroppedBids = droppedBids + } + if len(warnings) != 0 { + result.Warnings = append(result.Warnings, warnings...) + } + + responseExt := openrtb_ext.ExtBidResponse{} + // TODO use concrete structure + if len(payload.BidResponse.Ext) != 0 { + if err := json.Unmarshal(payload.BidResponse.Ext, &responseExt); err != nil { + result.Errors = append(result.Errors, "failed to unmarshal response.ext err: "+err.Error()) + } + } + + rctx.ResponseExt = responseExt + rctx.DefaultBids = m.addDefaultBids(&rctx, payload.BidResponse, responseExt) + + rctx.Trackers = tracker.CreateTrackers(rctx, payload.BidResponse) + + for bidder, responseTimeMs := range responseExt.ResponseTimeMillis { + rctx.BidderResponseTimeMillis[bidder.String()] = responseTimeMs + m.metricEngine.RecordPartnerResponseTimeStats(rctx.PubIDStr, string(bidder), responseTimeMs) + } + + // TODO: PBS-Core should pass the hostcookie for module to usersync.ParseCookieFromRequest() + rctx.MatchedImpression = getMatchedImpression(rctx) + matchedImpression, err := json.Marshal(rctx.MatchedImpression) + if err == nil { + responseExt.OwMatchedImpression = matchedImpression + } + + if rctx.SendAllBids { + responseExt.OwSendAllBids = 1 + } + + if rctx.LogInfoFlag == 1 { + responseExt.OwLogInfo = &openrtb_ext.OwLogInfo{ + // Logger: openwrap.GetLogAuctionObjectAsURL(ao, true, true), updated done later + Tracker: tracker.GetTrackerInfo(rctx, responseExt), + } + } + + // add seat-non-bids in the bidresponse only request.ext.prebid.returnallbidstatus is true + if rctx.ReturnAllBidStatus { + rctx.SeatNonBids = prepareSeatNonBids(rctx) + addSeatNonBidsInResponseExt(rctx, &responseExt) + } + + if rctx.Debug { + rCtxBytes, _ := json.Marshal(rctx) + result.DebugMessages = append(result.DebugMessages, string(rCtxBytes)) + } + + if rctx.Endpoint == models.EndpointWebS2S { + result.ChangeSet.AddMutation(func(ap hookstage.AuctionResponsePayload) (hookstage.AuctionResponsePayload, error) { + rctx := moduleCtx.ModuleContext["rctx"].(models.RequestCtx) + var err error + ap.BidResponse, err = tracker.InjectTrackers(rctx, ap.BidResponse) + if err == nil { + resetBidIdtoOriginal(ap.BidResponse) + } + return ap, err + }, hookstage.MutationUpdate, "response-body-with-webs2s-format") + return result, nil + } + + result.ChangeSet.AddMutation(func(ap hookstage.AuctionResponsePayload) (hookstage.AuctionResponsePayload, error) { + rctx := moduleCtx.ModuleContext["rctx"].(models.RequestCtx) + var err error + ap.BidResponse, err = m.updateORTBV25Response(rctx, ap.BidResponse) + if err != nil { + return ap, err + } + + ap.BidResponse, err = tracker.InjectTrackers(rctx, ap.BidResponse) + if err != nil { + return ap, err + } + + var responseExtjson json.RawMessage + responseExtjson, err = json.Marshal(responseExt) + if err != nil { + result.Errors = append(result.Errors, "failed to marshal response.ext err: "+err.Error()) + } + ap.BidResponse, err = m.applyDefaultBids(rctx, ap.BidResponse) + ap.BidResponse.Ext = responseExtjson + + resetBidIdtoOriginal(ap.BidResponse) + return ap, err + }, hookstage.MutationUpdate, "response-body-with-sshb-format") + + // TODO: move debug here + // result.ChangeSet.AddMutation(func(ap hookstage.AuctionResponsePayload) (hookstage.AuctionResponsePayload, error) { + // }, hookstage.MutationUpdate, "response-body-with-sshb-format") + + return result, nil +} + +func (m *OpenWrap) updateORTBV25Response(rctx models.RequestCtx, bidResponse *openrtb2.BidResponse) (*openrtb2.BidResponse, error) { + if len(bidResponse.SeatBid) == 0 { + return bidResponse, nil + } + + // remove non-winning bids if sendallbids=1 + if !rctx.SendAllBids { + for i := range bidResponse.SeatBid { + filteredBid := make([]openrtb2.Bid, 0, len(bidResponse.SeatBid[i].Bid)) + for _, bid := range bidResponse.SeatBid[i].Bid { + if b, ok := rctx.WinningBids[bid.ImpID]; ok && b.ID == bid.ID { + filteredBid = append(filteredBid, bid) + } + } + bidResponse.SeatBid[i].Bid = filteredBid + } + } + + // remove seats with empty bids (will add nobids later) + filteredSeatBid := make([]openrtb2.SeatBid, 0, len(bidResponse.SeatBid)) + for _, seatBid := range bidResponse.SeatBid { + if len(seatBid.Bid) > 0 { + filteredSeatBid = append(filteredSeatBid, seatBid) + } + } + bidResponse.SeatBid = filteredSeatBid + + // keep pubmatic 1st to handle automation failure. + if len(bidResponse.SeatBid) != 0 { + if bidResponse.SeatBid[0].Seat != "pubmatic" { + for i := 0; i < len(bidResponse.SeatBid); i++ { + if bidResponse.SeatBid[i].Seat == "pubmatic" { + temp := bidResponse.SeatBid[0] + bidResponse.SeatBid[0] = bidResponse.SeatBid[i] + bidResponse.SeatBid[i] = temp + } + } + } + } + + // update bid ext and other details + for i, seatBid := range bidResponse.SeatBid { + for j, bid := range seatBid.Bid { + impCtx, ok := rctx.ImpBidCtx[bid.ImpID] + if !ok { + continue + } + + bidCtx, ok := impCtx.BidCtx[bid.ID] + if !ok { + continue + } + + bidResponse.SeatBid[i].Bid[j].Ext, _ = json.Marshal(bidCtx.BidExt) + } + } + + return bidResponse, nil +} + +// isNewWinningBid calculates if the new bid (nbid) will win against the current winning bid (wbid) given preferDeals. +func isNewWinningBid(bid, wbid *models.OwBid, preferDeals bool) bool { + if preferDeals { + //only wbid has deal + if wbid.BidDealTierSatisfied && !bid.BidDealTierSatisfied { + bid.Nbr = GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid) + return false + } + //only bid has deal + if !wbid.BidDealTierSatisfied && bid.BidDealTierSatisfied { + wbid.Nbr = GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid) + return true + } + } + //both have deal or both do not have deal + if bid.NetEcpm > wbid.NetEcpm { + wbid.Nbr = GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid) + return true + } + bid.Nbr = GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid) + return false +} + +func getPlatformName(platform string) string { + if platform == models.PLATFORM_APP { + return models.PlatformAppTargetingKey + } + return platform +} + +func resetBidIdtoOriginal(bidResponse *openrtb2.BidResponse) { + for i, seatBid := range bidResponse.SeatBid { + for j, bid := range seatBid.Bid { + bidResponse.SeatBid[i].Bid[j].ID = utils.GetOriginalBidId(bid.ID) + } + } +} diff --git a/modules/pubmatic/openwrap/auctionresponsehook_test.go b/modules/pubmatic/openwrap/auctionresponsehook_test.go new file mode 100644 index 00000000000..b1800e33d4c --- /dev/null +++ b/modules/pubmatic/openwrap/auctionresponsehook_test.go @@ -0,0 +1,1291 @@ +package openwrap + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + mock_cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache/mock" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/tbf" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestSeatNonBidsInHandleAuctionResponseHook(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + type args struct { + ctx context.Context + moduleCtx hookstage.ModuleInvocationContext + payload hookstage.AuctionResponsePayload + } + + type want struct { + bidResponseExt json.RawMessage + err error + } + + tests := []struct { + name string + args args + want want + getMetricsEngine func() *mock_metrics.MockMetricsEngine + }{ + { + name: "returnallbidstatus_true", + args: args{ + ctx: nil, + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + StartTime: time.Now().UnixMilli(), + ReturnAllBidStatus: true, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + AdapterThrottleMap: map[string]struct{}{ + "pubmatic": {}, + }, + PubIDStr: "5890", + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{}, + }, + }, + }, + getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordNobidErrPrebidServerResponse("5890") + return mockEngine + }, + want: want{ + bidResponseExt: json.RawMessage(`{"prebid":{"seatnonbid":[{"nonbid":[{"impid":"imp1","statuscode":504,"ext":{"prebid":{"bid":{"id":""}}}}],"seat":"pubmatic","ext":null}]},"matchedimpression":{}}`), + }, + }, + { + name: "returnallbidstatus_false", + args: args{ + ctx: nil, + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + StartTime: time.Now().UnixMilli(), + ReturnAllBidStatus: false, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + AdapterThrottleMap: map[string]struct{}{ + "pubmatic": {}, + }, + PubIDStr: "5890", + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{}, + }, + }, + }, + getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordNobidErrPrebidServerResponse("5890") + return mockEngine + }, + want: want{ + bidResponseExt: json.RawMessage(`{"matchedimpression":{}}`), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := OpenWrap{ + metricEngine: tt.getMetricsEngine(), + } + hookResult, err := o.handleAuctionResponseHook(tt.args.ctx, tt.args.moduleCtx, tt.args.payload) + assert.Equal(t, tt.want.err, err, tt.name) + mutations := hookResult.ChangeSet.Mutations() + assert.NotEmpty(t, mutations, tt.name) + for _, mut := range mutations { + result, err := mut.Apply(tt.args.payload) + assert.Nil(t, err, tt.name) + assert.Equal(t, tt.want.bidResponseExt, result.BidResponse.Ext, tt.name) + } + }) + } +} + +func TestNonBRCodesInHandleAuctionResponseHook(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + type args struct { + ctx context.Context + moduleCtx hookstage.ModuleInvocationContext + payload hookstage.AuctionResponsePayload + } + type want struct { + impBidCtx map[string]models.ImpCtx + } + tests := []struct { + name string + args args + want want + getMetricsEngine func() *mock_metrics.MockMetricsEngine + }{ + { + name: "single bid and supportdeal is false", + args: args{ + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + StartTime: time.Now().UnixMilli(), + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + PubIDStr: "5890", + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 5, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + }, + getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "pubmatic") + return mockEngine + }, + want: want{ + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + NetECPM: 5, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 5, + EN: 5, + }, + }, + }, + }, + }, + }, + { + name: "test auction between 3 bids when supportdeal is false and no bid satisfies dealTier", + args: args{ + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + StartTime: time.Now().UnixMilli(), + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + PubIDStr: "5890", + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 5, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + ImpID: "imp1", + Price: 20, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "appnexus", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-3", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "rubicon", + }, + }, + }, + }, + }, + getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "pubmatic") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "appnexus") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "rubicon") + return mockEngine + }, + want: want{ + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + NetECPM: 5, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 5, + EN: 5, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 20, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 20, + EN: 20, + }, + "bid-id-3": { + BidExt: models.BidExt{ + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 10, + EN: 10, + }, + }, + }, + }, + }, + }, + { + name: "test auction between 3 bids when supportdeal is false and all bids satisfies dealTier", + args: args{ + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + StartTime: time.Now().UnixMilli(), + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + PubIDStr: "5890", + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 5, + Ext: json.RawMessage(`{"prebid":{"dealtiersatisfied":true}}`), + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + ImpID: "imp1", + Price: 20, + Ext: json.RawMessage(`{"prebid":{"dealtiersatisfied":true}}`), + }, + }, + Seat: "appnexus", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-3", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{"prebid":{"dealtiersatisfied":true}}`), + }, + }, + Seat: "rubicon", + }, + }, + }, + }, + }, + getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "pubmatic") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "appnexus") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "rubicon") + return mockEngine + }, + want: want{ + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + NetECPM: 5, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + Targeting: map[string]string{}, + }, + }, + }, + EG: 5, + EN: 5, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 20, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + Targeting: map[string]string{}, + }, + }, + }, + EG: 20, + EN: 20, + }, + "bid-id-3": { + BidExt: models.BidExt{ + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + Targeting: map[string]string{}, + }, + }, + }, + EG: 10, + EN: 10, + }, + }, + }, + }, + }, + }, + { + name: "single bid and supportdeal is true", + args: args{ + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + StartTime: time.Now().UnixMilli(), + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + PubIDStr: "5890", + SupportDeals: true, + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 5, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + }, + getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "pubmatic") + return mockEngine + }, + want: want{ + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + NetECPM: 5, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 5, + EN: 5, + }, + }, + }, + }, + }, + }, + { + name: "auction between 3 bids when supportdeal is true and no bid satisfies dealTier", + args: args{ + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + StartTime: time.Now().UnixMilli(), + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + PubIDStr: "5890", + SupportDeals: true, + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 20, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + ImpID: "imp1", + Price: 5, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "appnexus", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-3", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "rubicon", + }, + }, + }, + }, + }, + getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "pubmatic") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "appnexus") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "rubicon") + return mockEngine + }, + want: want{ + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + NetECPM: 20, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 20, + EN: 20, + }, + "bid-id-2": { + BidExt: models.BidExt{ + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + NetECPM: 5, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 5, + EN: 5, + }, + "bid-id-3": { + BidExt: models.BidExt{ + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 10, + EN: 10, + }, + }, + }, + }, + }, + }, + { + name: "auction between 3 bids when supportdeal is true and only middle bid satisfies dealTier", + args: args{ + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + StartTime: time.Now().UnixMilli(), + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + PubIDStr: "5890", + SupportDeals: true, + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 20, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + ImpID: "imp1", + Price: 5, + Ext: json.RawMessage(`{"prebid":{"dealtiersatisfied":true}}`), + }, + }, + Seat: "appnexus", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-3", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "rubicon", + }, + }, + }, + }, + }, + getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "pubmatic") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "appnexus") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "rubicon") + return mockEngine + }, + want: want{ + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + NetECPM: 20, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 20, + EN: 20, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 5, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + Targeting: map[string]string{}, + }, + }, + }, + EG: 5, + EN: 5, + }, + "bid-id-3": { + BidExt: models.BidExt{ + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 10, + EN: 10, + }, + }, + }, + }, + }, + }, + { + name: "auction between 3 bids when supportdeal is true and only last bid satisfies dealTier", + args: args{ + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + StartTime: time.Now().UnixMilli(), + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + PubIDStr: "5890", + SupportDeals: true, + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 20, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + ImpID: "imp1", + Price: 5, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "appnexus", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-3", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{"prebid":{"dealtiersatisfied":true}}`), + }, + }, + Seat: "rubicon", + }, + }, + }, + }, + }, + getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "pubmatic") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "appnexus") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "rubicon") + return mockEngine + }, + want: want{ + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + NetECPM: 20, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 20, + EN: 20, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 5, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 5, + EN: 5, + }, + "bid-id-3": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + Targeting: map[string]string{}, + }, + }, + NetECPM: 10, + }, + EG: 10, + EN: 10, + }, + }, + }, + }, + }, + }, + { + name: "auction between 3 bids when supportdeal is true and only first bid satisfies dealTier", + args: args{ + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + StartTime: time.Now().UnixMilli(), + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + PubIDStr: "5890", + SupportDeals: true, + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 20, + Ext: json.RawMessage(`{"prebid":{"dealtiersatisfied":true}}`), + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + ImpID: "imp1", + Price: 5, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "appnexus", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-3", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{}`), + }, + }, + Seat: "rubicon", + }, + }, + }, + }, + }, + getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "pubmatic") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "appnexus") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "rubicon") + return mockEngine + }, + want: want{ + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + Targeting: map[string]string{}, + }, + }, + NetECPM: 20, + }, + EG: 20, + EN: 20, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 5, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 5, + EN: 5, + }, + "bid-id-3": { + BidExt: models.BidExt{ + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Targeting: map[string]string{}, + }, + }, + }, + EG: 10, + EN: 10, + }, + }, + }, + }, + }, + }, + { + name: "auction between 3 bids when supportdeal is true and all bids satisfies dealTier", + args: args{ + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + StartTime: time.Now().UnixMilli(), + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": {}, + }, + PubIDStr: "5890", + SupportDeals: true, + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 20, + Ext: json.RawMessage(`{"prebid":{"dealtiersatisfied":true}}`), + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + ImpID: "imp1", + Price: 5, + Ext: json.RawMessage(`{"prebid":{"dealtiersatisfied":true}}`), + }, + }, + Seat: "appnexus", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-3", + ImpID: "imp1", + Price: 10, + Ext: json.RawMessage(`{"prebid":{"dealtiersatisfied":true}}`), + }, + }, + Seat: "rubicon", + }, + }, + }, + }, + }, + getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "pubmatic") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "appnexus") + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("", "5890", "rubicon") + return mockEngine + }, + want: want{ + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + Targeting: map[string]string{}, + }, + }, + NetECPM: 20, + }, + EG: 20, + EN: 20, + }, + "bid-id-2": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + Targeting: map[string]string{}, + }, + }, + NetECPM: 5, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + }, + EG: 5, + EN: 5, + }, + "bid-id-3": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + Targeting: map[string]string{}, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + NetECPM: 10, + }, + EG: 10, + EN: 10, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := OpenWrap{ + metricEngine: tt.getMetricsEngine(), + } + hookResult, _ := o.handleAuctionResponseHook(tt.args.ctx, tt.args.moduleCtx, tt.args.payload) + mutations := hookResult.ChangeSet.Mutations() + assert.NotEmpty(t, mutations, tt.name) + rctxInterface := hookResult.AnalyticsTags.Activities[0].Results[0].Values["request-ctx"] + rctx := rctxInterface.(*models.RequestCtx) + assert.Equal(t, tt.want.impBidCtx, rctx.ImpBidCtx, tt.name) + }) + } +} + +func TestResetBidIdtoOriginal(t *testing.T) { + type args struct { + bidResponse *openrtb2.BidResponse + } + tests := []struct { + name string + args args + want *openrtb2.BidResponse + }{ + { + name: "Reset Bid Id to original", + args: args{ + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "original::generated", + }, + { + ID: "original-1::generated-1", + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "original-2::generated-2", + }, + }, + Seat: "index", + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "original", + }, + { + ID: "original-1", + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "original-2", + }, + }, + Seat: "index", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetBidIdtoOriginal(tt.args.bidResponse) + assert.Equal(t, tt.want, tt.args.bidResponse, "Bid Id should reset to original") + }) + } +} + +func TestAuctionResponseHookForEndpointWebS2S(t *testing.T) { + ctrl := gomock.NewController(t) + mockCache := mock_cache.NewMockCache(ctrl) + tbf.Init(1, mockCache) + defer func() { + ctrl.Finish() + tbf.StopTBFReloaderService() + }() + + type args struct { + ctx context.Context + moduleCtx hookstage.ModuleInvocationContext + payload hookstage.AuctionResponsePayload + } + + type want struct { + bidResponse *openrtb2.BidResponse + err error + } + + tests := []struct { + name string + args args + want want + getMetricsEngine func() *mock_metrics.MockMetricsEngine + }{ + { + name: "inject_tracker_in_respose_for_WebS2S_endpoint", + args: args{ + ctx: nil, + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + Endpoint: models.EndpointWebS2S, + Trackers: map[string]models.OWTracker{ + "bid1": { + BidType: models.Video, + }, + }, + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: ``, + }, + }, + }, + }, + }, + }, + }, + want: want{ + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: "
"}, + }, + }, + }, + }, + err: nil, + }, + getMetricsEngine: func() *mock_metrics.MockMetricsEngine { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats(gomock.Any(), gomock.Any(), gomock.Any()) + mockEngine.EXPECT().RecordNobidErrPrebidServerResponse(gomock.Any()) + mockEngine.EXPECT().RecordPublisherResponseTimeStats(gomock.Any(), gomock.Any()) + return mockEngine + }, + }, + { + name: "inject_tracker_in_respose_and_reset_bidID_to_orignal_for_WebS2S_endpoint", + args: args{ + ctx: nil, + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + Endpoint: models.EndpointWebS2S, + Trackers: map[string]models.OWTracker{ + "bid1": { + BidType: models.Video, + }, + }, + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345:: 123422222225", + AdM: ``, + }, + }, + }, + }, + }, + }, + }, + want: want{ + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: "
"}, + }, + }, + }, + }, + err: nil, + }, + getMetricsEngine: func() *mock_metrics.MockMetricsEngine { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats(gomock.Any(), gomock.Any(), gomock.Any()) + mockEngine.EXPECT().RecordNobidErrPrebidServerResponse(gomock.Any()) + mockEngine.EXPECT().RecordPublisherResponseTimeStats(gomock.Any(), gomock.Any()) + return mockEngine + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := OpenWrap{ + metricEngine: tt.getMetricsEngine(), + cache: mockCache, + } + mockCache.EXPECT().GetTBFTrafficForPublishers().Return(map[int]map[int]int{1: {2: 3}}, nil).AnyTimes() + hookResult, err := o.handleAuctionResponseHook(tt.args.ctx, tt.args.moduleCtx, tt.args.payload) + assert.Equal(t, tt.want.err, err, tt.name) + mutations := hookResult.ChangeSet.Mutations() + assert.NotEmpty(t, mutations, tt.name) + for _, mut := range mutations { + result, err := mut.Apply(tt.args.payload) + assert.Nil(t, err, tt.name) + assert.Equal(t, tt.want.bidResponse, result.BidResponse, tt.name) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/beforevalidationhook.go b/modules/pubmatic/openwrap/beforevalidationhook.go new file mode 100644 index 00000000000..3f3ec3e2eb3 --- /dev/null +++ b/modules/pubmatic/openwrap/beforevalidationhook.go @@ -0,0 +1,975 @@ +package openwrap + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" + "strconv" + "strings" + + "github.com/buger/jsonparser" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adapters" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adunitconfig" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/bidderparams" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/boolutil" +) + +func (m OpenWrap) handleBeforeValidationHook( + ctx context.Context, + moduleCtx hookstage.ModuleInvocationContext, + payload hookstage.BeforeValidationRequestPayload, +) (hookstage.HookResult[hookstage.BeforeValidationRequestPayload], error) { + result := hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + } + + if len(moduleCtx.ModuleContext) == 0 { + result.DebugMessages = append(result.DebugMessages, "error: module-ctx not found in handleBeforeValidationHook()") + return result, nil + } + rCtx, ok := moduleCtx.ModuleContext["rctx"].(models.RequestCtx) + if !ok { + result.DebugMessages = append(result.DebugMessages, "error: request-ctx not found in handleBeforeValidationHook()") + return result, nil + } + defer func() { + moduleCtx.ModuleContext["rctx"] = rCtx + if result.Reject { + m.metricEngine.RecordBadRequests(rCtx.Endpoint, getPubmaticErrorCode(result.NbrCode)) + m.metricEngine.RecordNobidErrPrebidServerRequests(rCtx.PubIDStr, result.NbrCode) + } + }() + + // 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 + return result, nil + } + + if rCtx.Endpoint == models.EndpointHybrid { + //TODO: Add bidder params fix + result.Reject = false + return result, nil + } + + pubID, err := getPubID(*payload.BidRequest) + if err != nil { + result.NbrCode = nbr.InvalidPublisherID + result.Errors = append(result.Errors, "ErrInvalidPublisherID") + return result, fmt.Errorf("invalid publisher id : %v", err) + } + rCtx.PubID = pubID + rCtx.PubIDStr = strconv.Itoa(pubID) + rCtx.Source, rCtx.Origin = getSourceAndOrigin(payload.BidRequest) + rCtx.PageURL = getPageURL(payload.BidRequest) + rCtx.Platform = getPlatformFromRequest(payload.BidRequest) + rCtx.DevicePlatform = GetDevicePlatform(rCtx, payload.BidRequest) + + if rCtx.UidCookie == nil { + m.metricEngine.RecordUidsCookieNotPresentErrorStats(rCtx.PubIDStr, rCtx.ProfileIDStr) + } + m.metricEngine.RecordPublisherProfileRequests(rCtx.PubIDStr, rCtx.ProfileIDStr) + + requestExt, err := models.GetRequestExt(payload.BidRequest.Ext) + if err != nil { + result.NbrCode = nbr.InvalidRequestExt + err = errors.New("failed to get request ext: " + err.Error()) + result.Errors = append(result.Errors, err.Error()) + return result, err + } + rCtx.NewReqExt = requestExt + rCtx.ReturnAllBidStatus = requestExt.Prebid.ReturnAllBidStatus + + // TODO: verify preference of request.test vs queryParam test ++ this check is only for the CTV requests + if payload.BidRequest.Test != 0 { + rCtx.IsTestRequest = payload.BidRequest.Test + } + + partnerConfigMap, err := m.getProfileData(rCtx, *payload.BidRequest) + if err != nil || len(partnerConfigMap) == 0 { + // TODO: seperate DB fetch errors as internal errors + result.NbrCode = nbr.InvalidProfileConfiguration + if err != nil { + err = errors.New("failed to get profile data: " + err.Error()) + } else { + err = errors.New("failed to get profile data: received empty data") + } + result.Errors = append(result.Errors, err.Error()) + m.metricEngine.RecordPublisherInvalidProfileRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.ProfileIDStr) + m.metricEngine.RecordPublisherInvalidProfileImpressions(rCtx.PubIDStr, rCtx.ProfileIDStr, len(payload.BidRequest.Imp)) + return result, err + } + + rCtx.PartnerConfigMap = partnerConfigMap // keep a copy at module level as well + if ver, err := strconv.Atoi(models.GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap, models.DisplayVersionID)); err == nil { + rCtx.DisplayVersionID = ver + } + platform := rCtx.GetVersionLevelKey(models.PLATFORM_KEY) + if platform == "" { + result.NbrCode = nbr.InvalidPlatform + err = errors.New("failed to get platform data") + result.Errors = append(result.Errors, err.Error()) + m.metricEngine.RecordPublisherInvalidProfileRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.ProfileIDStr) + m.metricEngine.RecordPublisherInvalidProfileImpressions(rCtx.PubIDStr, rCtx.ProfileIDStr, len(payload.BidRequest.Imp)) + return result, err + } + rCtx.Platform = platform + rCtx.DevicePlatform = GetDevicePlatform(rCtx, payload.BidRequest) + rCtx.SendAllBids = isSendAllBids(rCtx) + rCtx.TMax = m.setTimeout(rCtx, payload.BidRequest) + + m.metricEngine.RecordPublisherRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.Platform) + + if newPartnerConfigMap, ok := ABTestProcessing(rCtx); ok { + rCtx.ABTestConfigApplied = 1 + rCtx.PartnerConfigMap = newPartnerConfigMap + result.Warnings = append(result.Warnings, "update the rCtx.PartnerConfigMap with ABTest data") + } + + var allPartnersThrottledFlag bool + rCtx.AdapterThrottleMap, allPartnersThrottledFlag = GetAdapterThrottleMap(rCtx.PartnerConfigMap) + if allPartnersThrottledFlag { + result.NbrCode = nbr.AllPartnerThrottled + result.Errors = append(result.Errors, "All adapters throttled") + rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) // for wrapper logger sz + return result, err + } + + priceGranularity, err := computePriceGranularity(rCtx) + if err != nil { + result.NbrCode = nbr.InvalidPriceGranularityConfig + err = errors.New("failed to price granularity details: " + err.Error()) + result.Errors = append(result.Errors, err.Error()) + rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) // for wrapper logger sz + return result, err + } + + rCtx.AdUnitConfig = m.cache.GetAdunitConfigFromCache(payload.BidRequest, rCtx.PubID, rCtx.ProfileID, rCtx.DisplayID) + + requestExt.Prebid.Debug = rCtx.Debug + // requestExt.Prebid.SupportDeals = rCtx.SupportDeals && rCtx.IsCTVRequest // TODO: verify usecase of Prefered deals vs Support details + requestExt.Prebid.AlternateBidderCodes, rCtx.MarketPlaceBidders = getMarketplaceBidders(requestExt.Prebid.AlternateBidderCodes, partnerConfigMap) + requestExt.Prebid.Targeting = &openrtb_ext.ExtRequestTargeting{ + PriceGranularity: &priceGranularity, + IncludeBidderKeys: boolutil.BoolPtr(true), + IncludeWinners: boolutil.BoolPtr(true), + } + + isAdPodRequest := false + disabledSlots := 0 + serviceSideBidderPresent := false + + aliasgvlids := make(map[string]uint16) + for i := 0; i < len(payload.BidRequest.Imp); i++ { + slotType := "banner" + var adpodExt *models.AdPod + var isAdPodImpression bool + imp := payload.BidRequest.Imp[i] + + impExt := &models.ImpExtension{} + if len(imp.Ext) != 0 { + err := json.Unmarshal(imp.Ext, impExt) + if err != nil { + result.NbrCode = nbr.InternalError + err = errors.New("failed to parse imp.ext: " + imp.ID) + result.Errors = append(result.Errors, err.Error()) + return result, err + } + } + if rCtx.Endpoint == models.EndpointWebS2S { + imp.TagID = getTagID(imp, impExt) + } + if imp.TagID == "" { + result.NbrCode = nbr.InvalidImpressionTagID + err = errors.New("tagid missing for imp: " + imp.ID) + result.Errors = append(result.Errors, err.Error()) + return result, err + } + + if imp.Video != nil { + slotType = "video" + + //add stats for video instl impressions + if imp.Instl == 1 { + m.metricEngine.RecordVideoInstlImpsStats(rCtx.PubIDStr, rCtx.ProfileIDStr) + } + if len(requestExt.Prebid.Macros) == 0 { + // provide custom macros for video event trackers + requestExt.Prebid.Macros = getVASTEventMacros(rCtx) + } + + if rCtx.IsCTVRequest && imp.Video.Ext != nil { + if _, _, _, err := jsonparser.Get(imp.Video.Ext, "adpod"); err == nil { + isAdPodImpression = true + if !isAdPodRequest { + isAdPodRequest = true + rCtx.MetricsEngine.RecordCTVReqCountWithAdPod(rCtx.PubIDStr, rCtx.ProfileIDStr) + } + } + } + } + + div := "" + if impExt.Wrapper != nil { + div = impExt.Wrapper.Div + } + + // reuse the existing impExt instead of allocating a new one + reward := impExt.Reward + if reward != nil { + impExt.Prebid.IsRewardedInventory = reward + } + // if imp.ext.data.pbadslot is absent then set it to tagId + if len(impExt.Data.PbAdslot) == 0 { + impExt.Data.PbAdslot = imp.TagID + } + + incomingSlots := getIncomingSlots(imp) + slotName := getSlotName(imp.TagID, impExt) + adUnitName := getAdunitName(imp.TagID, impExt) + + var videoAdUnitCtx, bannerAdUnitCtx models.AdUnitCtx + if rCtx.AdUnitConfig != nil { + // Currently we are supporting Video config via Ad Unit config file for in-app / video / display profiles + if (rCtx.Platform == models.PLATFORM_APP || rCtx.Platform == models.PLATFORM_VIDEO || rCtx.Platform == models.PLATFORM_DISPLAY) && imp.Video != nil { + if payload.BidRequest.App != nil && payload.BidRequest.App.Content != nil { + m.metricEngine.RecordReqImpsWithContentCount(rCtx.PubIDStr, models.ContentTypeApp) + } + if payload.BidRequest.Site != nil && payload.BidRequest.Site.Content != nil { + m.metricEngine.RecordReqImpsWithContentCount(rCtx.PubIDStr, models.ContentTypeSite) + } + } + videoAdUnitCtx = adunitconfig.UpdateVideoObjectWithAdunitConfig(rCtx, imp, div, payload.BidRequest.Device.ConnectionType) + bannerAdUnitCtx = adunitconfig.UpdateBannerObjectWithAdunitConfig(rCtx, imp, div) + } + + if !isSlotEnabled(videoAdUnitCtx, bannerAdUnitCtx) { + disabledSlots++ + + rCtx.ImpBidCtx[imp.ID] = models.ImpCtx{ // for wrapper logger sz + IncomingSlots: incomingSlots, + AdUnitName: adUnitName, + SlotName: slotName, + IsRewardInventory: reward, + } + continue + } + + bidderMeta := make(map[string]models.PartnerData) + nonMapped := make(map[string]struct{}) + for _, partnerConfig := range rCtx.PartnerConfigMap { + if partnerConfig[models.SERVER_SIDE_FLAG] != "1" { + continue + } + + partneridstr, ok := partnerConfig[models.PARTNER_ID] + if !ok { + continue + } + partnerID, err := strconv.Atoi(partneridstr) + if err != nil || partnerID == models.VersionLevelConfigID { + continue + } + + // bidderCode is in context with pubmatic. Ex. it could be appnexus-1, appnexus-2, etc. + bidderCode := partnerConfig[models.BidderCode] + // prebidBidderCode is equivalent of PBS-Core's bidderCode + prebidBidderCode := partnerConfig[models.PREBID_PARTNER_NAME] + // + rCtx.PrebidBidderCode[prebidBidderCode] = bidderCode + + if _, ok := rCtx.AdapterThrottleMap[bidderCode]; ok { + result.Warnings = append(result.Warnings, "Dropping throttled adapter from auction: "+bidderCode) + continue + } + + var isRegex bool + var slot, kgpv string + var bidderParams json.RawMessage + var matchedSlotKeysVAST []string + switch prebidBidderCode { + case string(openrtb_ext.BidderPubmatic), models.BidderPubMaticSecondaryAlias: + slot, kgpv, isRegex, bidderParams, err = bidderparams.PreparePubMaticParamsV25(rCtx, m.cache, *payload.BidRequest, imp, *impExt, partnerID) + case models.BidderVASTBidder: + slot, bidderParams, matchedSlotKeysVAST, err = bidderparams.PrepareVASTBidderParams(rCtx, m.cache, *payload.BidRequest, imp, *impExt, partnerID, adpodExt) + default: + slot, kgpv, isRegex, bidderParams, err = bidderparams.PrepareAdapterParamsV25(rCtx, m.cache, *payload.BidRequest, imp, *impExt, partnerID) + } + + if err != nil || len(bidderParams) == 0 { + result.Errors = append(result.Errors, fmt.Sprintf("no bidder params found for imp:%s partner: %s", imp.ID, prebidBidderCode)) + nonMapped[bidderCode] = struct{}{} + m.metricEngine.RecordPartnerConfigErrors(rCtx.PubIDStr, rCtx.ProfileIDStr, bidderCode, models.PartnerErrSlotNotMapped) + + if prebidBidderCode != string(openrtb_ext.BidderPubmatic) && prebidBidderCode != string(models.BidderPubMaticSecondaryAlias) { + continue + } + } + + m.metricEngine.RecordPlatformPublisherPartnerReqStats(rCtx.Platform, rCtx.PubIDStr, bidderCode) + + bidderMeta[bidderCode] = models.PartnerData{ + PartnerID: partnerID, + PrebidBidderCode: prebidBidderCode, + MatchedSlot: slot, // KGPSV + Params: bidderParams, + KGP: rCtx.PartnerConfigMap[partnerID][models.KEY_GEN_PATTERN], // acutual slot + KGPV: kgpv, // regex pattern, use this field for pubmatic default unmapped slot as well using isRegex + IsRegex: isRegex, // regex pattern + } + + for _, bidder := range matchedSlotKeysVAST { + bidderMeta[bidder].VASTTagFlags[bidder] = false + } + + if alias, ok := partnerConfig[models.IsAlias]; ok && alias == "1" { + if prebidPartnerName, ok := partnerConfig[models.PREBID_PARTNER_NAME]; ok { + rCtx.Aliases[bidderCode] = adapters.ResolveOWBidder(prebidPartnerName) + } + } + if alias, ok := IsAlias(bidderCode); ok { + rCtx.Aliases[bidderCode] = alias + } + + if partnerConfig[models.PREBID_PARTNER_NAME] == models.BidderVASTBidder { + updateAliasGVLIds(aliasgvlids, bidderCode, partnerConfig) + } + + serviceSideBidderPresent = true + } // for(rctx.PartnerConfigMap + + // update the imp.ext with bidder params for this + if impExt.Prebid.Bidder == nil { + impExt.Prebid.Bidder = make(map[string]json.RawMessage) + } + for bidder, meta := range bidderMeta { + impExt.Prebid.Bidder[bidder] = meta.Params + } + + impExt.Wrapper = nil + impExt.Reward = nil + impExt.Bidder = nil + newImpExt, err := json.Marshal(impExt) + if err != nil { + result.Errors = append(result.Errors, fmt.Sprintf("failed to update bidder params for impression %s", imp.ID)) + } + + // cache the details for further processing + if _, ok := rCtx.ImpBidCtx[imp.ID]; !ok { + rCtx.ImpBidCtx[imp.ID] = models.ImpCtx{ + ImpID: imp.ID, + TagID: imp.TagID, + Div: div, + IsRewardInventory: reward, + BidFloor: imp.BidFloor, + BidFloorCur: imp.BidFloorCur, + Type: slotType, + Banner: imp.Banner != nil, + Video: imp.Video, + Native: imp.Native, + IncomingSlots: incomingSlots, + Bidders: make(map[string]models.PartnerData), + BidCtx: make(map[string]models.BidCtx), + NewExt: json.RawMessage(newImpExt), + IsAdPodRequest: isAdPodRequest, + SlotName: slotName, + AdUnitName: adUnitName, + } + } + + if isAdPodImpression { + bidderMeta[string(openrtb_ext.BidderOWPrebidCTV)] = models.PartnerData{} + } + + impCtx := rCtx.ImpBidCtx[imp.ID] + impCtx.Bidders = bidderMeta + impCtx.NonMapped = nonMapped + impCtx.VideoAdUnitCtx = videoAdUnitCtx + impCtx.BannerAdUnitCtx = bannerAdUnitCtx + rCtx.ImpBidCtx[imp.ID] = impCtx + } // for(imp + + if disabledSlots == len(payload.BidRequest.Imp) { + result.NbrCode = nbr.AllSlotsDisabled + if err != nil { + err = errors.New("All slots disabled: " + err.Error()) + } else { + err = errors.New("All slots disabled") + } + result.Errors = append(result.Errors, err.Error()) + return result, nil + } + + if !serviceSideBidderPresent { + result.NbrCode = nbr.ServerSidePartnerNotConfigured + if err != nil { + err = errors.New("server side partner not found: " + err.Error()) + } else { + err = errors.New("server side partner not found") + } + result.Errors = append(result.Errors, err.Error()) + return result, nil + } + + if cto := setContentTransparencyObject(rCtx, requestExt); cto != nil { + requestExt.Prebid.Transparency = cto + } + + adunitconfig.UpdateFloorsExtObjectFromAdUnitConfig(rCtx, requestExt) + setFloorsExt(requestExt, rCtx.PartnerConfigMap) + + if len(rCtx.Aliases) != 0 && requestExt.Prebid.Aliases == nil { + requestExt.Prebid.Aliases = make(map[string]string) + } + for k, v := range rCtx.Aliases { + requestExt.Prebid.Aliases[k] = v + } + + requestExt.Prebid.AliasGVLIDs = aliasgvlids + if _, ok := rCtx.AdapterThrottleMap[string(openrtb_ext.BidderPubmatic)]; !ok { + requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, string(openrtb_ext.BidderPubmatic)) + } + + if _, ok := requestExt.Prebid.Aliases[string(models.BidderPubMaticSecondaryAlias)]; ok { + if _, ok := rCtx.AdapterThrottleMap[string(models.BidderPubMaticSecondaryAlias)]; !ok { + requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, string(models.BidderPubMaticSecondaryAlias)) + } + } + + // similar to impExt, reuse the existing requestExt to avoid additional memory requests + requestExt.Wrapper = nil + requestExt.Bidder = nil + + if rCtx.Debug { + newImp, _ := json.Marshal(rCtx.ImpBidCtx) + result.DebugMessages = append(result.DebugMessages, "new imp: "+string(newImp)) + newReqExt, _ := json.Marshal(rCtx.NewReqExt) + result.DebugMessages = append(result.DebugMessages, "new request.ext: "+string(newReqExt)) + } + + result.ChangeSet.AddMutation(func(ep hookstage.BeforeValidationRequestPayload) (hookstage.BeforeValidationRequestPayload, error) { + rctx := moduleCtx.ModuleContext["rctx"].(models.RequestCtx) + var err error + ep.BidRequest, err = m.applyProfileChanges(rctx, ep.BidRequest) + if err != nil { + result.Errors = append(result.Errors, "failed to apply profile changes: "+err.Error()) + } + return ep, err + }, hookstage.MutationUpdate, "request-body-with-profile-data") + + result.Reject = false + return result, nil +} + +// applyProfileChanges copies and updates BidRequest with required values from http header and partnetConfigMap +func (m *OpenWrap) applyProfileChanges(rctx models.RequestCtx, bidRequest *openrtb2.BidRequest) (*openrtb2.BidRequest, error) { + if rctx.IsTestRequest > 0 { + bidRequest.Test = 1 + } + + if cur, ok := rctx.PartnerConfigMap[models.VersionLevelConfigID][models.AdServerCurrency]; ok { + bidRequest.Cur = append(bidRequest.Cur, cur) + } + if bidRequest.TMax == 0 { + bidRequest.TMax = rctx.TMax + } + + if bidRequest.Source == nil { + bidRequest.Source = &openrtb2.Source{} + } + bidRequest.Source.TID = bidRequest.ID + + for i := 0; i < len(bidRequest.Imp); i++ { + // TODO: move this to PBS-Core + if bidRequest.Imp[i].BidFloor == 0 { + bidRequest.Imp[i].BidFloorCur = "" + } else if bidRequest.Imp[i].BidFloorCur == "" { + bidRequest.Imp[i].BidFloorCur = "USD" + } + + m.applyBannerAdUnitConfig(rctx, &bidRequest.Imp[i]) + m.applyVideoAdUnitConfig(rctx, &bidRequest.Imp[i]) + bidRequest.Imp[i].Ext = rctx.ImpBidCtx[bidRequest.Imp[i].ID].NewExt + } + + if rctx.Platform == models.PLATFORM_APP || rctx.Platform == models.PLATFORM_VIDEO { + sChainObj := getSChainObj(rctx.PartnerConfigMap) + if sChainObj != nil { + setSchainInSourceObject(bidRequest.Source, sChainObj) + } + } + + adunitconfig.ReplaceAppObjectFromAdUnitConfig(rctx, bidRequest.App) + adunitconfig.ReplaceDeviceTypeFromAdUnitConfig(rctx, &bidRequest.Device) + + bidRequest.Device.IP = rctx.IP + bidRequest.Device.Language = getValidLanguage(bidRequest.Device.Language) + validateDevice(bidRequest.Device) + + if bidRequest.User == nil { + bidRequest.User = &openrtb2.User{} + } + if bidRequest.User.CustomData == "" && rctx.KADUSERCookie != nil { + bidRequest.User.CustomData = rctx.KADUSERCookie.Value + } + for i := 0; i < len(bidRequest.WLang); i++ { + bidRequest.WLang[i] = getValidLanguage(bidRequest.WLang[i]) + } + + if bidRequest.Site != nil && bidRequest.Site.Content != nil { + bidRequest.Site.Content.Language = getValidLanguage(bidRequest.Site.Content.Language) + } else if bidRequest.App != nil && bidRequest.App.Content != nil { + bidRequest.App.Content.Language = getValidLanguage(bidRequest.App.Content.Language) + } + + var err error + var requestExtjson json.RawMessage + if rctx.NewReqExt != nil { + requestExtjson, err = json.Marshal(rctx.NewReqExt) + bidRequest.Ext = requestExtjson + } + return bidRequest, err +} + +func (m *OpenWrap) applyVideoAdUnitConfig(rCtx models.RequestCtx, imp *openrtb2.Imp) { + if imp.Video == nil { + return + } + + adUnitCfg := rCtx.ImpBidCtx[imp.ID].VideoAdUnitCtx.AppliedSlotAdUnitConfig + if adUnitCfg == nil { + return + } + + impBidCtx := rCtx.ImpBidCtx[imp.ID] + if imp.BidFloor == 0 && adUnitCfg.BidFloor != nil { + imp.BidFloor = *adUnitCfg.BidFloor + impBidCtx.BidFloor = imp.BidFloor + } + + if len(imp.BidFloorCur) == 0 && adUnitCfg.BidFloorCur != nil { + imp.BidFloorCur = *adUnitCfg.BidFloorCur + impBidCtx.BidFloorCur = imp.BidFloorCur + } + rCtx.ImpBidCtx[imp.ID] = impBidCtx + + if adUnitCfg.Exp != nil { + imp.Exp = int64(*adUnitCfg.Exp) + } + + if adUnitCfg.Video == nil { + return + } + + //check if video is disabled, if yes then remove video from imp object + if adUnitCfg.Video.Enabled != nil && !*adUnitCfg.Video.Enabled { + imp.Video = nil + return + } + + if adUnitCfg.Video.Config == nil { + return + } + + configObjInVideoConfig := adUnitCfg.Video.Config + + if len(imp.Video.MIMEs) == 0 { + imp.Video.MIMEs = configObjInVideoConfig.MIMEs + } + + if imp.Video.MinDuration == 0 { + imp.Video.MinDuration = configObjInVideoConfig.MinDuration + } + + if imp.Video.MaxDuration == 0 { + imp.Video.MaxDuration = configObjInVideoConfig.MaxDuration + } + + if imp.Video.Skip == nil { + imp.Video.Skip = configObjInVideoConfig.Skip + } + + if imp.Video.SkipMin == 0 { + imp.Video.SkipMin = configObjInVideoConfig.SkipMin + } + + if imp.Video.SkipAfter == 0 { + imp.Video.SkipAfter = configObjInVideoConfig.SkipAfter + } + + if len(imp.Video.BAttr) == 0 { + imp.Video.BAttr = configObjInVideoConfig.BAttr + } + + if imp.Video.MinBitRate == 0 { + imp.Video.MinBitRate = configObjInVideoConfig.MinBitRate + } + + if imp.Video.MaxBitRate == 0 { + imp.Video.MaxBitRate = configObjInVideoConfig.MaxBitRate + } + + if imp.Video.MaxExtended == 0 { + imp.Video.MaxExtended = configObjInVideoConfig.MaxExtended + } + + if imp.Video.StartDelay == nil { + imp.Video.StartDelay = configObjInVideoConfig.StartDelay + } + + if imp.Video.Placement == 0 { + imp.Video.Placement = configObjInVideoConfig.Placement + } + + if imp.Video.Plcmt == 0 { + imp.Video.Plcmt = configObjInVideoConfig.Plcmt + } + + if imp.Video.Linearity == 0 { + imp.Video.Linearity = configObjInVideoConfig.Linearity + } + + if imp.Video.Protocol == 0 { + imp.Video.Protocol = configObjInVideoConfig.Protocol + } + + if len(imp.Video.Protocols) == 0 { + imp.Video.Protocols = configObjInVideoConfig.Protocols + } + + if imp.Video.W == 0 { + imp.Video.W = configObjInVideoConfig.W + } + + if imp.Video.H == 0 { + imp.Video.H = configObjInVideoConfig.H + } + + if imp.Video.Sequence == 0 { + imp.Video.Sequence = configObjInVideoConfig.Sequence + } + + if imp.Video.BoxingAllowed == 0 { + imp.Video.BoxingAllowed = configObjInVideoConfig.BoxingAllowed + } + + if len(imp.Video.PlaybackMethod) == 0 { + imp.Video.PlaybackMethod = configObjInVideoConfig.PlaybackMethod + } + + if imp.Video.PlaybackEnd == 0 { + imp.Video.PlaybackEnd = configObjInVideoConfig.PlaybackEnd + } + + if imp.Video.Delivery == nil { + imp.Video.Delivery = configObjInVideoConfig.Delivery + } + + if imp.Video.Pos == nil { + imp.Video.Pos = configObjInVideoConfig.Pos + } + + if len(imp.Video.API) == 0 { + imp.Video.API = configObjInVideoConfig.API + } + + if len(imp.Video.CompanionType) == 0 { + imp.Video.CompanionType = configObjInVideoConfig.CompanionType + } + + if imp.Video.CompanionAd == nil { + imp.Video.CompanionAd = configObjInVideoConfig.CompanionAd + } +} + +func (m *OpenWrap) applyBannerAdUnitConfig(rCtx models.RequestCtx, imp *openrtb2.Imp) { + if imp.Banner == nil { + return + } + + adUnitCfg := rCtx.ImpBidCtx[imp.ID].BannerAdUnitCtx.AppliedSlotAdUnitConfig + if adUnitCfg == nil { + return + } + + impBidCtx := rCtx.ImpBidCtx[imp.ID] + if imp.BidFloor == 0 && adUnitCfg.BidFloor != nil { + imp.BidFloor = *adUnitCfg.BidFloor + impBidCtx.BidFloor = imp.BidFloor + } + + if len(imp.BidFloorCur) == 0 && adUnitCfg.BidFloorCur != nil { + imp.BidFloorCur = *adUnitCfg.BidFloorCur + impBidCtx.BidFloorCur = imp.BidFloorCur + } + rCtx.ImpBidCtx[imp.ID] = impBidCtx + + if adUnitCfg.Exp != nil { + imp.Exp = int64(*adUnitCfg.Exp) + } + + if adUnitCfg.Banner == nil { + return + } + + if adUnitCfg.Banner.Enabled != nil && !*adUnitCfg.Banner.Enabled { + imp.Banner = nil + return + } +} + +/* +getSlotName will return slot name according to below priority + 1. imp.ext.gpid + 2. imp.tagid + 3. imp.ext.data.pbadslot + 4. imp.ext.prebid.storedrequest.id +*/ +func getSlotName(tagId string, impExt *models.ImpExtension) string { + if impExt == nil { + return tagId + } + + if len(impExt.GpId) > 0 { + return impExt.GpId + } + + if len(tagId) > 0 { + return tagId + } + + if len(impExt.Data.PbAdslot) > 0 { + return impExt.Data.PbAdslot + } + + var storeReqId string + if impExt.Prebid.StoredRequest != nil { + storeReqId = impExt.Prebid.StoredRequest.ID + } + + return storeReqId +} + +/* +getAdunitName will return adunit name according to below priority + 1. imp.ext.data.adserver.adslot if imp.ext.data.adserver.name == "gam" + 2. imp.ext.data.pbadslot + 3. imp.tagid +*/ +func getAdunitName(tagId string, impExt *models.ImpExtension) string { + if impExt == nil { + return tagId + } + if impExt.Data.AdServer != nil && impExt.Data.AdServer.Name == models.GamAdServer && impExt.Data.AdServer.AdSlot != "" { + return impExt.Data.AdServer.AdSlot + } + if len(impExt.Data.PbAdslot) > 0 { + return impExt.Data.PbAdslot + } + return tagId +} + +func getDomainFromUrl(pageUrl string) string { + u, err := url.Parse(pageUrl) + if err != nil { + return "" + } + + return u.Host +} + +// always perfer rCtx.LoggerImpressionID received in request. Create a new once if it is not availble. +// func getLoggerID(reqExt models.ExtRequestWrapper) string { +// if reqExt.Wrapper.LoggerImpressionID != "" { +// return reqExt.Wrapper.LoggerImpressionID +// } +// return uuid.NewV4().String() +// } + +// NYC: make this generic. Do we need this?. PBS now has auto_gen_source_tid generator. We can make it to wiid for pubmatic adapter in pubmatic.go +func updateRequestExtBidderParamsPubmatic(bidderParams json.RawMessage, cookie, loggerID, bidderCode string) (json.RawMessage, error) { + bidderParamsMap := make(map[string]map[string]interface{}) + _ = json.Unmarshal(bidderParams, &bidderParamsMap) // ignore error, incoming might be nil for now but we still have data to put + + bidderParamsMap[bidderCode] = map[string]interface{}{ + models.WrapperLoggerImpID: loggerID, + } + + if len(cookie) != 0 { + bidderParamsMap[bidderCode][models.COOKIE] = cookie + } + + return json.Marshal(bidderParamsMap) +} + +func getPageURL(bidRequest *openrtb2.BidRequest) string { + if bidRequest.App != nil && bidRequest.App.StoreURL != "" { + return bidRequest.App.StoreURL + } else if bidRequest.Site != nil && bidRequest.Site.Page != "" { + return bidRequest.Site.Page + } + return "" +} + +// getVASTEventMacros populates macros with PubMatic specific macros +// These marcros is used in replacing with actual values of Macros in case of Video Event tracke URLs +// If this function fails to determine value of any macro then it continues with next macro setup +// returns true when at least one macro is added to map +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.MacroUnixTimeStamp): fmt.Sprintf("%d", rctx.StartTime), + string(models.MacroPlatform): fmt.Sprintf("%d", rctx.DevicePlatform), + string(models.MacroWrapperImpressionID): rctx.LoggerImpressionID, + } + + if rctx.SSAI != "" { + macros[string(models.MacroSSAI)] = rctx.SSAI + } + + return macros +} + +func updateAliasGVLIds(aliasgvlids map[string]uint16, bidderCode string, partnerConfig map[string]string) { + if vendorID, ok := partnerConfig[models.VENDORID]; ok && vendorID != "" { + vid, err := strconv.ParseUint(vendorID, 10, 64) + if err != nil { + return + } + + if vid == 0 { + return + } + aliasgvlids[bidderCode] = uint16(vid) + } +} + +// setTimeout - This utility returns timeout applicable for a profile +func (m OpenWrap) setTimeout(rCtx models.RequestCtx, req *openrtb2.BidRequest) int64 { + var auctionTimeout int64 + + // BidRequest.TMax has highest priority + if req.TMax != 0 { + auctionTimeout = req.TMax + } else { + //check for ssTimeout in the partner config + ssTimeout := models.GetVersionLevelPropertyFromPartnerConfig(rCtx.PartnerConfigMap, models.SSTimeoutKey) + if ssTimeout != "" { + ssTimeoutDB, err := strconv.Atoi(ssTimeout) + if err == nil { + auctionTimeout = int64(ssTimeoutDB) + } + } + } + + // found tmax value in request or db + if auctionTimeout != 0 { + if auctionTimeout < m.cfg.Timeout.MinTimeout { + return m.cfg.Timeout.MinTimeout + } else if auctionTimeout > m.cfg.Timeout.MaxTimeout { + return m.cfg.Timeout.MaxTimeout + } + return auctionTimeout + } + + //Below piece of code is applicable for older profiles where ssTimeout is not set + //Here we will check the partner timeout and select max timeout considering timeout range + auctionTimeout = m.cfg.Timeout.MinTimeout + for _, partnerConfig := range rCtx.PartnerConfigMap { + partnerTO, _ := strconv.Atoi(partnerConfig[models.TIMEOUT]) + if int64(partnerTO) > m.cfg.Timeout.MaxTimeout { + auctionTimeout = m.cfg.Timeout.MaxTimeout + break + } + if int64(partnerTO) >= m.cfg.Timeout.MinTimeout && auctionTimeout < int64(partnerTO) { + auctionTimeout = int64(partnerTO) + + } + } + return auctionTimeout +} + +// isSendAllBids returns true in below cases: +// if ssauction flag is set 0 in the request +// if ssauction flag is not set and platform is dislay, then by default send all bids +// if ssauction flag is not set and platform is in-app, then check if profile setting sendAllBids is set to 1 +func isSendAllBids(rctx models.RequestCtx) bool { + //for webs2s endpoint SendAllBids is always true + if rctx.Endpoint == models.EndpointWebS2S { + return true + } + //if ssauction is set to 0 in the request + if rctx.SSAuction == 0 { + return true + } else if rctx.SSAuction == -1 && rctx.Platform == models.PLATFORM_APP { + // if platform is in-app, then check if profile setting sendAllBids is set to 1 + if models.GetVersionLevelPropertyFromPartnerConfig(rctx.PartnerConfigMap, models.SendAllBidsKey) == "1" { + return true + } + } + return false +} + +func getValidLanguage(language string) string { + if len(language) > 2 { + lang := language[0:2] + if models.ValidCode(lang) { + return lang + } + } + return language +} + +func isSlotEnabled(videoAdUnitCtx, bannerAdUnitCtx models.AdUnitCtx) bool { + videoEnabled := true + if videoAdUnitCtx.AppliedSlotAdUnitConfig != nil && videoAdUnitCtx.AppliedSlotAdUnitConfig.Video != nil && + videoAdUnitCtx.AppliedSlotAdUnitConfig.Video.Enabled != nil && !*videoAdUnitCtx.AppliedSlotAdUnitConfig.Video.Enabled { + videoEnabled = false + } + + bannerEnabled := true + if bannerAdUnitCtx.AppliedSlotAdUnitConfig != nil && bannerAdUnitCtx.AppliedSlotAdUnitConfig.Banner != nil && + bannerAdUnitCtx.AppliedSlotAdUnitConfig.Banner.Enabled != nil && !*bannerAdUnitCtx.AppliedSlotAdUnitConfig.Banner.Enabled { + bannerEnabled = false + } + + return videoEnabled || bannerEnabled +} + +func getPubID(bidRequest openrtb2.BidRequest) (pubID int, err error) { + if bidRequest.Site != nil && bidRequest.Site.Publisher != nil { + pubID, err = strconv.Atoi(bidRequest.Site.Publisher.ID) + } else if bidRequest.App != nil && bidRequest.App.Publisher != nil { + pubID, err = strconv.Atoi(bidRequest.App.Publisher.ID) + } + return pubID, err +} + +func getTagID(imp openrtb2.Imp, impExt *models.ImpExtension) string { + //priority for tagId is imp.ext.gpid > imp.TagID > imp.ext.data.pbadslot + if impExt.GpId != "" { + if idx := strings.Index(impExt.GpId, "#"); idx != -1 { + return impExt.GpId[:idx] + } + return impExt.GpId + } else if imp.TagID != "" { + return imp.TagID + } + return impExt.Data.PbAdslot +} diff --git a/modules/pubmatic/openwrap/beforevalidationhook_test.go b/modules/pubmatic/openwrap/beforevalidationhook_test.go new file mode 100644 index 00000000000..0678d1884b3 --- /dev/null +++ b/modules/pubmatic/openwrap/beforevalidationhook_test.go @@ -0,0 +1,2972 @@ +package openwrap + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "testing" + + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + ow_adapters "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adapters" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + mock_cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +var rctx = models.RequestCtx{ + ProfileID: 1234, + DisplayID: 1, + SSAuction: -1, + Platform: "in-app", + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: false, + TrackerEndpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + UidCookie: &http.Cookie{ + Name: "uids", + Value: `eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=`, + }, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: `7D75D25F-FAC9-443D-B2D1-B17FEE11E027`, + }, + OriginCookie: "go-test", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: "1234", + Endpoint: models.EndpointV25, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), +} + +func getTestBidRequest(isSite bool) *openrtb2.BidRequest { + + testReq := &openrtb2.BidRequest{} + + testReq.ID = "testID" + + testReq.Imp = []openrtb2.Imp{ + { + ID: "testImp1", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + }, + Video: &openrtb2.Video{ + W: 200, + H: 300, + Plcmt: 1, + }, + }, + } + if !isSite { + testReq.App = &openrtb2.App{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + Content: &openrtb2.Content{ + Language: "english", + }, + } + } else { + testReq.Site = &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + Content: &openrtb2.Content{ + Language: "english", + }, + } + } + testReq.Cur = []string{"EUR"} + testReq.WLang = []string{"english", "hindi"} + testReq.Device = &openrtb2.Device{ + DeviceType: 1, + Language: "english", + } + return testReq +} + +func TestGetPageURL(t *testing.T) { + type args struct { + bidRequest *openrtb2.BidRequest + } + tests := []struct { + name string + args args + want string + }{ + { + name: "App_storeurl_is_not_empty", + args: args{ + bidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{ + StoreURL: "testurlApp", + }, + }, + }, + want: "testurlApp", + }, + { + name: "Site_page_is_not_empty", + args: args{ + bidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Page: "testurlSite", + }, + }, + }, + want: "testurlSite", + }, + { + name: "both_app_and_site_are_nil", + args: args{ + bidRequest: &openrtb2.BidRequest{}, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getPageURL(tt.args.bidRequest) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetVASTEventMacros(t *testing.T) { + type args struct { + rctx models.RequestCtx + } + tests := []struct { + name string + args args + want map[string]string + }{ + { + name: "SSAI_is_empty", + args: args{ + rctx: models.RequestCtx{ + ProfileID: 1234, + DisplayID: 1234, + StartTime: 1234, + DevicePlatform: 1234, + LoggerImpressionID: "1234", + SSAI: "", + }, + }, + want: map[string]string{ + "[PROFILE_ID]": "1234", + "[PROFILE_VERSION]": "1234", + "[UNIX_TIMESTAMP]": "1234", + "[PLATFORM]": "1234", + "[WRAPPER_IMPRESSION_ID]": "1234", + }, + }, + { + name: "SSAI_is_not_empty", + args: args{ + rctx: models.RequestCtx{ + ProfileID: 1234, + DisplayID: 1234, + StartTime: 1234, + DevicePlatform: 1234, + LoggerImpressionID: "1234", + SSAI: "1234", + }, + }, + want: map[string]string{ + "[PROFILE_ID]": "1234", + "[PROFILE_VERSION]": "1234", + "[UNIX_TIMESTAMP]": "1234", + "[PLATFORM]": "1234", + "[WRAPPER_IMPRESSION_ID]": "1234", + "[SSAI]": "1234", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getVASTEventMacros(tt.args.rctx) + assert.Equal(t, tt.want, got) + + }) + } +} + +func TestUpdateAliasGVLIds(t *testing.T) { + type args struct { + aliasgvlids map[string]uint16 + bidderCode string + partnerConfig map[string]string + } + type want struct { + aliasgvlids map[string]uint16 + } + tests := []struct { + name string + args args + want want + }{ + { + name: "vendorId not present in config", + args: args{ + aliasgvlids: map[string]uint16{}, + bidderCode: "vastbidder1", + partnerConfig: map[string]string{}, + }, + want: want{ + aliasgvlids: map[string]uint16{}, + }, + }, + { + name: "Empty vendorID", + args: args{ + aliasgvlids: map[string]uint16{}, + bidderCode: "vastbidder1", + partnerConfig: map[string]string{models.VENDORID: ""}, + }, + want: want{ + aliasgvlids: map[string]uint16{}, + }, + }, + { + name: "Error parsing vendorID", + args: args{ + aliasgvlids: map[string]uint16{}, + bidderCode: "vastbidder1", + partnerConfig: map[string]string{models.VENDORID: "abc"}, + }, + }, + { + name: "VendorID is 0", + args: args{ + aliasgvlids: map[string]uint16{}, + bidderCode: "vastbidder1", + partnerConfig: map[string]string{models.VENDORID: "0"}, + }, + want: want{ + aliasgvlids: map[string]uint16{}, + }, + }, + { + name: "Negative vendorID", + args: args{ + aliasgvlids: map[string]uint16{}, + bidderCode: "vastbidder1", + partnerConfig: map[string]string{models.VENDORID: "-76"}, + }, + }, + { + name: "Valid vendorID", + args: args{ + aliasgvlids: map[string]uint16{}, + bidderCode: "vastbidder1", + partnerConfig: map[string]string{models.VENDORID: "76"}, + }, + want: want{ + aliasgvlids: map[string]uint16{"vastbidder1": uint16(76)}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + updateAliasGVLIds(tt.args.aliasgvlids, tt.args.bidderCode, tt.args.partnerConfig) + }) + } +} + +func TestOpenWrap_setTimeout(t *testing.T) { + type fields struct { + cfg config.Config + cache cache.Cache + metricEngine metrics.MetricsEngine + } + type args struct { + rCtx models.RequestCtx + bidRequest *openrtb2.BidRequest + } + tests := []struct { + name string + fields fields + args args + want int64 + }{ + { + name: "Highest_priority_to_request_tmax_parameter", + args: args{ + rCtx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + "ssTimeout": "250", + }, + }, + }, + bidRequest: &openrtb2.BidRequest{ + TMax: 220, + }, + }, + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + MinTimeout: 200, + MaxTimeout: 300, + }, + }, + }, + want: 220, + }, + { + name: "tmax_parameter_less_than_minTimeout", + args: args{ + rCtx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + "ssTimeout": "250", + }, + }, + }, + bidRequest: &openrtb2.BidRequest{ + TMax: 10, + }, + }, + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + MinTimeout: 200, + MaxTimeout: 300, + }, + }, + }, + want: 200, + }, + { + name: "ssTimeout_greater_than_minTimeout_and_less_than_maxTimeout", + args: args{ + rCtx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + "ssTimeout": "250", + }, + }, + }, + bidRequest: &openrtb2.BidRequest{}, + }, + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + MinTimeout: 200, + MaxTimeout: 300, + }, + }, + }, + want: 250, + }, + { + name: "ssTimeout_less_than_minTimeout", + args: args{ + rCtx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + "ssTimeout": "250", + }, + }, + }, + bidRequest: &openrtb2.BidRequest{}, + }, + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + MinTimeout: 300, + MaxTimeout: 400, + }, + }, + }, + want: 300, + }, + { + name: "ssTimeout_greater_than_minTimeout_and_also_greater_than_maxTimeout", + args: args{ + rCtx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + "ssTimeout": "500", + }, + }, + }, + bidRequest: &openrtb2.BidRequest{}, + }, + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + MinTimeout: 300, + MaxTimeout: 400, + }, + }, + }, + want: 400, + }, + { + name: "ssTimeout_greater_than_minTimeout_and_less_than_maxTimeout", + args: args{ + rCtx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + "ssTimeout": "400", + }, + }, + }, + bidRequest: &openrtb2.BidRequest{}, + }, + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + MinTimeout: 300, + MaxTimeout: 500, + }, + }, + }, + want: 400, + }, + //Below piece of code is applicable for older profiles where ssTimeout is not set + //Here we will check the partner timeout and select max timeout considering timeout range + { + name: "at_lease_one_partner_timeout_greater_than_cofig_maxTimeout", + args: args{ + rCtx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + 1: { + "timeout": "500", + }, + 2: { + "timeout": "250", + }, + }, + }, + bidRequest: &openrtb2.BidRequest{}, + }, + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + MinTimeout: 200, + MaxTimeout: 300, + }, + }, + }, + want: 300, + }, + { + name: "all_partner_timeout_less_than_cofig_maxTimeout", + args: args{ + rCtx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + 1: { + "timeout": "230", + }, + 2: { + "timeout": "250", + }, + 3: { + "timeout": "280", + }, + }, + }, + bidRequest: &openrtb2.BidRequest{}, + }, + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + MinTimeout: 200, + MaxTimeout: 300, + }, + }, + }, + want: 280, + }, + { + name: "all_partner_timeout_less_than_cofig_minTimeout", + args: args{ + rCtx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + 1: { + "timeout": "100", + }, + 2: { + "timeout": "150", + }, + 3: { + "timeout": "180", + }, + }, + }, + bidRequest: &openrtb2.BidRequest{}, + }, + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + MinTimeout: 200, + MaxTimeout: 300, + }, + }, + }, + want: 200, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := OpenWrap{ + cfg: tt.fields.cfg, + cache: tt.fields.cache, + metricEngine: tt.fields.metricEngine, + } + got := m.setTimeout(tt.args.rCtx, tt.args.bidRequest) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestIsSendAllBids(t *testing.T) { + type args struct { + rctx models.RequestCtx + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "sendallbids_always_true_for_webs2s_endpoint", + args: args{ + rctx: models.RequestCtx{ + Endpoint: models.EndpointWebS2S, + }, + }, + want: true, + }, + { + name: "Don't_do_ssauction", + args: args{ + rctx: models.RequestCtx{ + SSAuction: 0, + }, + }, + want: true, + }, + { + name: "SSAuction_flag_not_set_In-app_sendAllbids_flag_1", + args: args{ + rctx: models.RequestCtx{ + SSAuction: -1, + Platform: models.PLATFORM_APP, + PartnerConfigMap: map[int]map[string]string{ + -1: { + "sendAllBids": "1", + }, + }, + }, + }, + want: true, + }, + { + name: "SSAuction_flag_not_set_In-app_sendAllbids_flag_other_than_1", + args: args{ + rctx: models.RequestCtx{ + SSAuction: -1, + Platform: models.PLATFORM_APP, + PartnerConfigMap: map[int]map[string]string{ + -1: { + "sendAllBids": "5", + }, + }, + }, + }, + want: false, + }, + { + name: "Random_value_of_ssauctionflag", + args: args{ + rctx: models.RequestCtx{ + SSAuction: 5, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isSendAllBids(tt.args.rctx) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetValidLanguage(t *testing.T) { + type args struct { + language string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Language_of_length_less_than_2", + args: args{ + language: "te", + }, + want: "te", + }, + { + name: "Language_of_length_greater_than_2_and_it_is_valid_code", + args: args{ + language: "hindi", + }, + want: "hi", + }, + { + name: "Language_of_length_greater_than_2_and_it_is_Invalid_code", + args: args{ + language: "xyz", + }, + want: "xyz", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getValidLanguage(tt.args.language) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestIsSlotEnabled(t *testing.T) { + type args struct { + videoAdUnitCtx models.AdUnitCtx + bannerAdUnitCtx models.AdUnitCtx + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Video_enabled_in_Video_adunit_context", + args: args{ + videoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + }, + }, + }, + }, + want: true, + }, + { + name: "Banner_enabled_in_banner_adunit_context", + args: args{ + bannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + }, + }, + }, + }, + want: true, + }, + { + name: "both_banner_and_video_enabled_in_adunit_context", + args: args{ + bannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + }, + }, + }, + videoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + }, + }, + }, + }, + want: true, + }, + { + name: "both_banner_and_video_disabled_in_adunit_context", + args: args{ + bannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + videoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isSlotEnabled(tt.args.videoAdUnitCtx, tt.args.bannerAdUnitCtx) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetPubID(t *testing.T) { + type args struct { + bidRequest openrtb2.BidRequest + } + type want struct { + wantErr bool + pubID int + } + tests := []struct { + name string + args args + want want + }{ + { + name: "publisher_id_present_in_site_object_and_it_is_valid_integer", + args: args{ + bidRequest: openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "5890", + }, + }, + }, + }, + want: want{ + wantErr: false, + pubID: 5890, + }, + }, + { + name: "publisher_id_present_in_site_object_but_it_is_not_valid_integer", + args: args{ + bidRequest: openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "test", + }, + }, + }, + }, + want: want{ + wantErr: true, + pubID: 0, + }, + }, + { + name: "publisher_id_present_in_App_object_and_it_is_valid_integer", + args: args{ + bidRequest: openrtb2.BidRequest{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ + ID: "5890", + }, + }, + }, + }, + want: want{ + wantErr: false, + pubID: 5890, + }, + }, + { + name: "publisher_id_present_in_App_object_but_it_is_not_valid_integer", + args: args{ + bidRequest: openrtb2.BidRequest{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ + ID: "test", + }, + }, + }, + }, + want: want{ + wantErr: true, + pubID: 0, + }, + }, + { + name: "publisher_id_present_in_both_Site_and_App_object", + args: args{ + bidRequest: openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "1234", + }, + }, + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ + ID: "5890", + }, + }, + }, + }, + want: want{ + wantErr: false, + pubID: 1234, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getPubID(tt.args.bidRequest) + if (err != nil) != tt.want.wantErr { + assert.Equal(t, tt.want.wantErr, err != nil) + return + } + if got != tt.want.pubID { + assert.Equal(t, tt.want.pubID, got) + } + }) + } +} + +func TestOpenWrap_applyProfileChanges(t *testing.T) { + type fields struct { + cfg config.Config + cache cache.Cache + metricEngine metrics.MetricsEngine + } + type args struct { + rctx models.RequestCtx + bidRequest *openrtb2.BidRequest + } + tests := []struct { + name string + fields fields + args args + want *openrtb2.BidRequest + wantErr bool + }{ + { + name: "Request_with_App_object", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 1, + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AdServerCurrency: "USD", + models.SChainDBKey: "1", + }, + }, + TMax: 500, + IP: "127.0.0.1", + Platform: models.PLATFORM_APP, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: "123456789", + }, + }, + bidRequest: getTestBidRequest(false), + }, + want: &openrtb2.BidRequest{ + ID: "testID", + Test: 1, + Cur: []string{"EUR", "USD"}, + TMax: 500, + Source: &openrtb2.Source{ + TID: "testID", + }, + Imp: []openrtb2.Imp{ + { + ID: "testImp1", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + }, + Video: &openrtb2.Video{ + W: 200, + H: 300, + Plcmt: 1, + }, + }, + }, + Device: &openrtb2.Device{ + IP: "127.0.0.1", + Language: "en", + DeviceType: 1, + }, + WLang: []string{"en", "hi"}, + User: &openrtb2.User{ + CustomData: "123456789", + }, + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + Content: &openrtb2.Content{ + Language: "en", + }, + }, + }, + wantErr: false, + }, + { + name: "Request_with_Site_object", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 1, + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AdServerCurrency: "USD", + models.SChainDBKey: "1", + }, + }, + TMax: 500, + IP: "127.0.0.1", + Platform: models.PLATFORM_APP, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: "123456789", + }, + }, + bidRequest: getTestBidRequest(true), + }, + want: &openrtb2.BidRequest{ + ID: "testID", + Test: 1, + Cur: []string{"EUR", "USD"}, + TMax: 500, + Source: &openrtb2.Source{ + TID: "testID", + }, + Imp: []openrtb2.Imp{ + { + ID: "testImp1", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + }, + Video: &openrtb2.Video{ + W: 200, + H: 300, + Plcmt: 1, + }, + }, + }, + Device: &openrtb2.Device{ + IP: "127.0.0.1", + Language: "en", + DeviceType: 1, + }, + WLang: []string{"en", "hi"}, + User: &openrtb2.User{ + CustomData: "123456789", + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + Content: &openrtb2.Content{ + Language: "en", + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &OpenWrap{ + cfg: tt.fields.cfg, + cache: tt.fields.cache, + metricEngine: tt.fields.metricEngine, + } + got, err := m.applyProfileChanges(tt.args.rctx, tt.args.bidRequest) + if (err != nil) != tt.wantErr { + assert.Equal(t, tt.wantErr, err != nil) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestOpenWrap_applyVideoAdUnitConfig(t *testing.T) { + type fields struct { + cfg config.Config + cache cache.Cache + metricEngine metrics.MetricsEngine + } + type args struct { + rCtx models.RequestCtx + imp *openrtb2.Imp + } + type want struct { + rCtx models.RequestCtx + imp *openrtb2.Imp + } + tests := []struct { + name string + fields fields + args args + want want + }{ + { + name: "imp.video_is_nil", + args: args{ + imp: &openrtb2.Imp{ + Video: nil, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + Video: nil, + }, + }, + }, + { + name: "empty_adunitCfg", + args: args{ + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: nil, + }, + }, + }, + }, + imp: &openrtb2.Imp{ + ID: "testImp", + Video: &openrtb2.Video{}, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + ID: "testImp", + Video: &openrtb2.Video{}, + }, + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: nil, + }, + }, + }, + }, + }, + }, + { + name: "imp.BidFloor_and_BidFloorCur_updated_from_adunit_config", + args: args{ + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr(2.0), + BidFloorCur: ptrutil.ToPtr("USD"), + }, + }, + }, + }, + }, + imp: &openrtb2.Imp{ + ID: "testImp", + BidFloor: 0, + BidFloorCur: "", + Video: &openrtb2.Video{}, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + ID: "testImp", + Video: &openrtb2.Video{}, + BidFloor: 2.0, + BidFloorCur: "USD", + }, + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr(2.0), + BidFloorCur: ptrutil.ToPtr("USD"), + }, + }, + BidFloor: 2, + BidFloorCur: "USD", + }, + }, + }, + }, + }, + { + name: "imp.Exp_updated_from_adunit_config", + args: args{ + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Exp: ptrutil.ToPtr(10), + }, + }, + }, + }, + }, + imp: &openrtb2.Imp{ + ID: "testImp", + Video: &openrtb2.Video{}, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + ID: "testImp", + Video: &openrtb2.Video{}, + Exp: 10, + }, + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Exp: ptrutil.ToPtr(10), + }, + }, + }, + }, + }, + }, + }, + { + name: "imp_has_video_object_but_adunitConfig_video_is_nil._imp_video_will_not_be_updated", + args: args{ + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: nil, + }, + }, + }, + }, + }, + imp: &openrtb2.Imp{ + ID: "testImp", + Video: &openrtb2.Video{ + W: 200, + H: 300, + }, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + ID: "testImp", + Video: &openrtb2.Video{ + W: 200, + H: 300, + }, + }, + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: nil, + }, + }, + }, + }, + }, + }, + }, + { + name: "imp_has_video_object_but_video_is_disabled_from_adunitConfig_then_remove_video_object_from_imp", + args: args{ + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + }, + }, + imp: &openrtb2.Imp{ + ID: "testImp", + Video: &openrtb2.Video{ + W: 200, + H: 300, + }, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + ID: "testImp", + Video: nil, + }, + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "imp_has_empty_video_object_and_adunitCofig_for_video_is_enable._all_absent_video_parameters_will_be_updated", + args: args{ + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + MinDuration: 10, + MaxDuration: 40, + Skip: ptrutil.ToPtr(int8(1)), + SkipMin: 5, + SkipAfter: 10, + Plcmt: 1, + Placement: 1, + MinBitRate: 100, + MaxBitRate: 200, + MaxExtended: 50, + Linearity: 1, + Protocol: 1, + W: 640, + H: 480, + Sequence: 2, + BoxingAllowed: 1, + PlaybackEnd: 2, + MIMEs: []string{"mimes"}, + API: []adcom1.APIFramework{1, 2}, + Delivery: []adcom1.DeliveryMethod{1, 2}, + PlaybackMethod: []adcom1.PlaybackMethod{1, 2}, + BAttr: []adcom1.CreativeAttribute{1, 2}, + StartDelay: ptrutil.ToPtr(adcom1.StartDelay(2)), + Protocols: []adcom1.MediaCreativeSubtype{1, 2}, + Pos: ptrutil.ToPtr(adcom1.PlacementPosition(1)), + CompanionType: []adcom1.CompanionType{1, 2}, + }, + }, + }, + }, + }, + }, + }, + }, + imp: &openrtb2.Imp{ + ID: "testImp", + Video: &openrtb2.Video{}, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + ID: "testImp", + Video: &openrtb2.Video{ + W: 640, + H: 480, + MinDuration: 10, + MaxDuration: 40, + Skip: ptrutil.ToPtr(int8(1)), + SkipMin: 5, + SkipAfter: 10, + Plcmt: 1, + Placement: 1, + MinBitRate: 100, + MaxBitRate: 200, + MaxExtended: 50, + Linearity: 1, + Protocol: 1, + Sequence: 2, + BoxingAllowed: 1, + PlaybackEnd: 2, + MIMEs: []string{"mimes"}, + API: []adcom1.APIFramework{1, 2}, + Delivery: []adcom1.DeliveryMethod{1, 2}, + PlaybackMethod: []adcom1.PlaybackMethod{1, 2}, + BAttr: []adcom1.CreativeAttribute{1, 2}, + StartDelay: ptrutil.ToPtr(adcom1.StartDelay(2)), + Protocols: []adcom1.MediaCreativeSubtype{1, 2}, + Pos: ptrutil.ToPtr(adcom1.PlacementPosition(1)), + CompanionType: []adcom1.CompanionType{1, 2}, + }, + }, + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + MinDuration: 10, + MaxDuration: 40, + Skip: ptrutil.ToPtr(int8(1)), + SkipMin: 5, + SkipAfter: 10, + Plcmt: 1, + Placement: 1, + MinBitRate: 100, + MaxBitRate: 200, + MaxExtended: 50, + Linearity: 1, + Protocol: 1, + W: 640, + H: 480, + Sequence: 2, + BoxingAllowed: 1, + PlaybackEnd: 2, + MIMEs: []string{"mimes"}, + API: []adcom1.APIFramework{1, 2}, + Delivery: []adcom1.DeliveryMethod{1, 2}, + PlaybackMethod: []adcom1.PlaybackMethod{1, 2}, + BAttr: []adcom1.CreativeAttribute{1, 2}, + StartDelay: ptrutil.ToPtr(adcom1.StartDelay(2)), + Protocols: []adcom1.MediaCreativeSubtype{1, 2}, + Pos: ptrutil.ToPtr(adcom1.PlacementPosition(1)), + CompanionType: []adcom1.CompanionType{1, 2}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "imp_has_video_object_and_adunitConfig_alos_have_parameter_present_then_priority_to_request", + args: args{ + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + MinDuration: 10, + MaxDuration: 40, + Skip: ptrutil.ToPtr(int8(1)), + SkipMin: 5, + SkipAfter: 10, + }, + }, + }, + }, + }, + }, + }, + }, + imp: &openrtb2.Imp{ + ID: "testImp", + Video: &openrtb2.Video{ + W: 640, + H: 480, + MinDuration: 20, + MaxDuration: 60, + Skip: ptrutil.ToPtr(int8(2)), + SkipMin: 10, + SkipAfter: 20, + }, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + ID: "testImp", + Video: &openrtb2.Video{ + W: 640, + H: 480, + MinDuration: 20, + MaxDuration: 60, + Skip: ptrutil.ToPtr(int8(2)), + SkipMin: 10, + SkipAfter: 20, + }, + }, + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + VideoAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + MinDuration: 10, + MaxDuration: 40, + Skip: ptrutil.ToPtr(int8(1)), + SkipMin: 5, + SkipAfter: 10, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &OpenWrap{ + cfg: tt.fields.cfg, + cache: tt.fields.cache, + metricEngine: tt.fields.metricEngine, + } + m.applyVideoAdUnitConfig(tt.args.rCtx, tt.args.imp) + assert.Equal(t, tt.args.imp, tt.want.imp, "Imp video is not upadted as expected from adunit config") + assert.Equal(t, tt.args.rCtx, tt.want.rCtx, "rctx is not upadted as expected from adunit config") + }) + } +} + +func TestOpenWrap_applyBannerAdUnitConfig(t *testing.T) { + type fields struct { + cfg config.Config + cache cache.Cache + metricEngine metrics.MetricsEngine + } + type args struct { + rCtx models.RequestCtx + imp *openrtb2.Imp + } + type want struct { + rCtx models.RequestCtx + imp *openrtb2.Imp + } + tests := []struct { + name string + fields fields + args args + want want + }{ + { + name: "imp.banner_is_nil", + args: args{ + imp: &openrtb2.Imp{ + Banner: nil, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + Banner: nil, + }, + }, + }, + { + name: "empty_adunitCfg", + args: args{ + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + BannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: nil, + }, + }, + }, + }, + imp: &openrtb2.Imp{ + ID: "testImp", + Banner: &openrtb2.Banner{}, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + ID: "testImp", + Banner: &openrtb2.Banner{}, + }, + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + BannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: nil, + }, + }, + }, + }, + }, + }, + { + name: "imp.BidFloor_and_BidFloorCur_updated_from_adunit_config", + args: args{ + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + BannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr(2.0), + BidFloorCur: ptrutil.ToPtr("USD"), + }, + }, + }, + }, + }, + imp: &openrtb2.Imp{ + ID: "testImp", + BidFloor: 0, + BidFloorCur: "", + Banner: &openrtb2.Banner{}, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + ID: "testImp", + Banner: &openrtb2.Banner{}, + BidFloor: 2.0, + BidFloorCur: "USD", + }, + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + BannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + BidFloor: ptrutil.ToPtr(2.0), + BidFloorCur: ptrutil.ToPtr("USD"), + }, + }, + BidFloor: 2, + BidFloorCur: "USD", + }, + }, + }, + }, + }, + { + name: "imp.Exp_updated_from_adunit_config", + args: args{ + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + BannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Exp: ptrutil.ToPtr(10), + }, + }, + }, + }, + }, + imp: &openrtb2.Imp{ + ID: "testImp", + Banner: &openrtb2.Banner{}, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + ID: "testImp", + Banner: &openrtb2.Banner{}, + Exp: 10, + }, + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + BannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Exp: ptrutil.ToPtr(10), + }, + }, + }, + }, + }, + }, + }, + { + name: "imp_has_banner_object_but_adunitConfig_banner_is_nil._imp_banner_will_not_be_updated", + args: args{ + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + BannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: nil, + }, + }, + }, + }, + }, + imp: &openrtb2.Imp{ + ID: "testImp", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + }, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + ID: "testImp", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + }, + }, + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + BannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: nil, + }, + }, + }, + }, + }, + }, + }, + { + name: "imp_has_banner_object_but_banner_is_disabled_from_adunitConfig_then_remove_banner_object_from_imp", + args: args{ + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + BannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + }, + }, + imp: &openrtb2.Imp{ + ID: "testImp", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + }, + }, + }, + want: want{ + imp: &openrtb2.Imp{ + ID: "testImp", + Banner: nil, + }, + rCtx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "testImp": { + BannerAdUnitCtx: models.AdUnitCtx{ + AppliedSlotAdUnitConfig: &adunitconfig.AdConfig{ + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &OpenWrap{ + cfg: tt.fields.cfg, + cache: tt.fields.cache, + metricEngine: tt.fields.metricEngine, + } + m.applyBannerAdUnitConfig(tt.args.rCtx, tt.args.imp) + assert.Equal(t, tt.args.imp, tt.want.imp, "Imp banner is not upadted as expected from adunit config") + assert.Equal(t, tt.args.rCtx, tt.want.rCtx, "rctx is not upadted as expected from adunit config") + }) + } +} + +func TestGetDomainFromUrl(t *testing.T) { + type args struct { + pageUrl string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test_case_1", + args: args{ + pageUrl: "http://ebay.com/inte/automation/s2s/pwt_parameter_validation_muti_slot_multi_size.html?pwtvc=1&pwtv=1&profileid=3277", + }, + want: "ebay.com", + }, + { + name: "test_case_2", + args: args{ + pageUrl: "http://ebay.co.in/inte/automation/s2s/pwt_parameter_validation_muti_slot_multi_size.html?pwtvc=1&pwtv=1&profileid=3277", + }, + want: "ebay.co.in", + }, + { + name: "test_case_3", + args: args{ + pageUrl: "site@sit.com", + }, + want: "", + }, + { + name: "test_case_4", + args: args{ + pageUrl: " 12 44", + }, + want: "", + }, + { + name: "test_case_5", + args: args{ + pageUrl: " ", + }, + want: "", + }, + { + name: "test_case_6", + args: args{ + pageUrl: "", + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getDomainFromUrl(tt.args.pageUrl); got != tt.want { + assert.Equal(t, tt.want, got) + } + }) + } +} + +func TestUpdateRequestExtBidderParamsPubmatic(t *testing.T) { + type args struct { + bidderParams json.RawMessage + cookie string + loggerID string + bidderCode string + } + tests := []struct { + name string + args args + want json.RawMessage + wantErr bool + }{ + { + name: "empty_cookie", + args: args{ + bidderParams: json.RawMessage(`{"pubmatic":{"pmzoneid":"zone1","adSlot":"38519891"}}`), + loggerID: "b441a46e-8c1f-428b-9c29-44e2a408a954", + bidderCode: "pubmatic", + }, + want: json.RawMessage(`{"pubmatic":{"wiid":"b441a46e-8c1f-428b-9c29-44e2a408a954"}}`), + wantErr: false, + }, + { + name: "empty_loggerID", + args: args{ + bidderParams: json.RawMessage(`{"pubmatic":{"pmzoneid":"zone1","adSlot":"38519891"}}`), + cookie: "test_cookie", + bidderCode: "pubmatic", + }, + want: json.RawMessage(`{"pubmatic":{"Cookie":"test_cookie","wiid":""}}`), + }, + { + name: "both_cookie_and_loogerID_are_empty", + args: args{ + bidderParams: json.RawMessage(`{"pubmatic":{"pmzoneid":"zone1","adSlot":"38519891"}}`), + cookie: "", + loggerID: "", + bidderCode: "pubmatic", + }, + want: json.RawMessage(`{"pubmatic":{"wiid":""}}`), + }, + { + name: "both_cookie_and_loogerID_are_present", + args: args{ + bidderParams: json.RawMessage(`{"pubmatic":{"pmzoneid":"zone1","adSlot":"38519891"}}`), + cookie: "test_cookie", + loggerID: "b441a46e-8c1f-428b-9c29-44e2a408a954", + bidderCode: "pubmatic", + }, + want: json.RawMessage(`{"pubmatic":{"Cookie":"test_cookie","wiid":"b441a46e-8c1f-428b-9c29-44e2a408a954"}}`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := updateRequestExtBidderParamsPubmatic(tt.args.bidderParams, tt.args.cookie, tt.args.loggerID, tt.args.bidderCode) + if (err != nil) != tt.wantErr { + assert.Equal(t, tt.wantErr, err != nil) + return + } + assert.Equal(t, tt.want, got) + + }) + } +} + +func TestOpenWrap_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 + } + tests := []struct { + name string + fields fields + args args + want hookstage.HookResult[hookstage.BeforeValidationRequestPayload] + setup func() + isRequestNotRejected bool + wantErr bool + }{ + { + name: "request_with_sshb=1", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + Sshb: "1", + }, + }, + }, + bidrequest: 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":{"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"}}}`), + }, + want: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: false, + }, + }, + { + name: "empty_module_context", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{}, + bidrequest: 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":{"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, + }, + want: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + DebugMessages: []string{"error: module-ctx not found in handleBeforeValidationHook()"}, + }, + wantErr: false, + }, + { + name: "rctx_is_not_present", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "test_rctx": "test", + }, + }, + bidrequest: 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":{"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, + }, + want: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + DebugMessages: []string{"error: request-ctx not found in handleBeforeValidationHook()"}, + }, + wantErr: false, + }, + { + name: "hybrid_request_module_should_not_reject_request_and_return_without_executing_module", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + Endpoint: models.EndpointHybrid, + }, + }, + }, + bidrequest: 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":{"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"}}}`), + }, + want: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: false, + }, + wantErr: false, + }, + { + name: "Invalid_PubID_in_request", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": rctx, + }, + }, + bidrequest: 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","ext":{"wrapper":{"div":"div"},"bidder":{"pubmatic":{"keywords":[{"key":"pmzoneid","value":["val1","val2"]}]}},"prebid":{}}}],"site":{"domain":"test.com","page":"www.test.com","publisher":{"id":"test"}},"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":{"profileid":123,"versionid":1,"wiid":"test_display_wiid"}}}`), + }, + fields: fields{ + cache: mockCache, + metricEngine: mockEngine, + }, + setup: func() { + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidPublisherID)) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("", nbr.InvalidPublisherID) + }, + want: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + NbrCode: nbr.InvalidPublisherID, + Errors: []string{"ErrInvalidPublisherID"}, + }, + wantErr: true, + }, + { + name: "Invalid_request_ext", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": rctx, + }, + }, + bidrequest: 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","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":1}`), + }, + fields: fields{ + cache: mockCache, + metricEngine: mockEngine, + }, + setup: func() { + mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidRequestExt)) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", nbr.InvalidRequestExt) + }, + want: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + NbrCode: nbr.InvalidRequestExt, + Errors: []string{"failed to get request ext: failed to decode request.ext : json: cannot unmarshal number into Go value of type models.RequestExt"}, + }, + wantErr: true, + }, + { + name: "Error_in_getting_profile_data", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": rctx, + }, + }, + bidrequest: 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","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")) + //prometheus metrics + 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: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + NbrCode: nbr.InvalidProfileConfiguration, + Errors: []string{"failed to get profile data: test"}, + }, + wantErr: true, + }, + { + name: "got_empty_profileData_from_DB", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": rctx, + }, + }, + bidrequest: 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","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{}, nil) + //prometheus metrics + 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: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + NbrCode: nbr.InvalidProfileConfiguration, + Errors: []string{"failed to get profile data: received empty data"}, + }, + wantErr: true, + }, + { + name: "platform_is_not_present_in_request_then_reject_the_request", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": rctx, + }, + }, + bidrequest: 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","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: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + NbrCode: nbr.InvalidPlatform, + Errors: []string{"failed to get platform data"}, + }, + wantErr: true, + }, + { + name: "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","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":"","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: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + NbrCode: nbr.AllPartnerThrottled, + Errors: []string{"All adapters throttled"}, + }, + wantErr: false, + }, + { + name: "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","banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900},"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","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", + 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: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + NbrCode: nbr.InvalidImpressionTagID, + Errors: []string{"tagid missing for imp: 123"}, + }, + wantErr: true, + }, + { + name: "TagID_not_present_in_imp_and_not_found_for_client_request", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": func() models.RequestCtx { + testRctx := rctx + testRctx.Endpoint = models.EndpointWebS2S + return testRctx + }(), + }, + }, + bidrequest: 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},"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", + 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(models.EndpointWebS2S, getPubmaticErrorCode(nbr.InvalidImpressionTagID)) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", nbr.InvalidImpressionTagID) + mockEngine.EXPECT().RecordPublisherRequests(models.EndpointWebS2S, "5890", rctx.Platform) + }, + want: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + NbrCode: nbr.InvalidImpressionTagID, + Errors: []string{"tagid missing for imp: 123"}, + }, + wantErr: true, + }, + { + name: "invalid_impExt", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": rctx, + }, + }, + bidrequest: 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","ext":"1"}],"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", + 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: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + NbrCode: nbr.InternalError, + Errors: []string{"failed to parse imp.ext: 123"}, + }, + wantErr: true, + }, + { + name: "allSotsDisabled", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + ProfileID: 1234, + DisplayID: 1, + SSAuction: -1, + Platform: "in-app", + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: false, + TrackerEndpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + UidCookie: &http.Cookie{ + Name: "uids", + Value: `eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=`, + }, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: `7D75D25F-FAC9-443D-B2D1-B17FEE11E027`, + }, + OriginCookie: "go-test", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: "1234", + Endpoint: models.EndpointV25, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + MetricsEngine: mockEngine, + }, + }, + }, + bidrequest: 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","ext":{"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", + models.PLATFORM_KEY: models.PLATFORM_APP, + }, + }, nil) + mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_@_W_x_H_", + Config: map[string]*adunitconfig.AdConfig{ + "adunit@700x900": { + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(false), + }, + }, + "adunit@640x480": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(false), + }, + }, + }, + }) + //prometheus metrics + mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.AllSlotsDisabled)) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", nbr.AllSlotsDisabled) + mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) + mockEngine.EXPECT().RecordImpDisabledViaConfigStats(models.ImpTypeVideo, "5890", "1234") + mockEngine.EXPECT().RecordImpDisabledViaConfigStats(models.ImpTypeBanner, "5890", "1234") + }, + want: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + NbrCode: nbr.AllSlotsDisabled, + Errors: []string{"All slots disabled"}, + }, + wantErr: false, + }, + { + name: "no_serviceSideBidderPresent", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + ProfileID: 1234, + DisplayID: 1, + SSAuction: -1, + Platform: "in-app", + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: false, + TrackerEndpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + UidCookie: &http.Cookie{ + Name: "uids", + Value: `eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=`, + }, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: `7D75D25F-FAC9-443D-B2D1-B17FEE11E027`, + }, + OriginCookie: "go-test", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: "1234", + Endpoint: models.EndpointV25, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + MetricsEngine: mockEngine, + }, + }, + }, + bidrequest: 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","ext":{"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.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.ServerSidePartnerNotConfigured)) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", nbr.ServerSidePartnerNotConfigured) + mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) + }, + want: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: true, + NbrCode: nbr.ServerSidePartnerNotConfigured, + Errors: []string{"server side partner not found"}, + }, + wantErr: false, + }, + { + name: "happy_path_request_not_rejected_and_successfully_updted_from_DB", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + ProfileID: 1234, + DisplayID: 1, + SSAuction: -1, + Platform: "in-app", + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: false, + TrackerEndpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + UidCookie: &http.Cookie{ + Name: "uids", + Value: `eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=`, + }, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: `7D75D25F-FAC9-443D-B2D1-B17FEE11E027`, + }, + OriginCookie: "go-test", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: "1234", + Endpoint: models.EndpointV25, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + MetricsEngine: mockEngine, + }, + }, + }, + bidrequest: 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":{"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().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "adunit@700x900": { + SlotName: "adunit@700x900", + SlotMappings: map[string]interface{}{ + models.SITE_CACHE_KEY: "12313", + models.TAG_CACHE_KEY: "45343", + }, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"adunit@700x900"}, + HashValueMap: map[string]string{ + "adunit@700x900": "1232433543534543", + }, + }) + 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().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) + mockEngine.EXPECT().RecordPlatformPublisherPartnerReqStats(rctx.Platform, "5890", "appnexus") + }, + want: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: false, + NbrCode: 0, + ChangeSet: hookstage.ChangeSet[hookstage.BeforeValidationRequestPayload]{}, + DebugMessages: []string{"new imp: {\"123\":{\"ImpID\":\"\",\"TagID\":\"adunit\",\"Div\":\"\",\"Secure\":0,\"IsRewardInventory\":null,\"Banner\":true,\"Video\":{\"mimes\":[\"video/mp4\",\"video/mpeg\"],\"w\":640,\"h\":480},\"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\"}}},\"NonMapped\":{},\"NewExt\":{\"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}}}", "new request.ext: {\"prebid\":{\"bidderparams\":{\"pubmatic\":{\"wiid\":\"\"}},\"debug\":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},\"floors\":{\"enforcement\":{\"enforcepbs\":true},\"enabled\":true},\"macros\":{\"[PLATFORM]\":\"3\",\"[PROFILE_ID]\":\"1234\",\"[PROFILE_VERSION]\":\"1\",\"[UNIX_TIMESTAMP]\":\"0\",\"[WRAPPER_IMPRESSION_ID]\":\"\"}}}"}, + AnalyticsTags: hookanalytics.Analytics{}, + }, + wantErr: false, + isRequestNotRejected: true, + }, + { + name: "prebid-validation-errors-imp-missing", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + PubIDStr: "1234", + Endpoint: models.EndpointV25, + }, + }, + }, + bidrequest: json.RawMessage(`{}`), + }, + fields: fields{ + cache: mockCache, + metricEngine: mockEngine, + }, + setup: func() { + mockEngine.EXPECT().RecordBadRequests(models.EndpointV25, 18) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("1234", 604) + }, + want: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{}, + wantErr: false, + }, + { + name: "prebid-validation-errors-site-and-app-missing", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + PubIDStr: "1234", + Endpoint: models.EndpointV25, + }, + }, + }, + bidrequest: 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":{"bidder":{"pubmatic":{"keywords":[{"key":"pmzoneid","value":["val1","val2"]}]}},"prebid":{}}}],"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() { + mockEngine.EXPECT().RecordBadRequests(models.EndpointV25, 18) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("1234", 604) + }, + want: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + ow_adapters.InitBidders(tt.fields.cfg) + m := OpenWrap{ + cfg: tt.fields.cfg, + cache: tt.fields.cache, + metricEngine: tt.fields.metricEngine, + } + bidrequest := &openrtb2.BidRequest{} + json.Unmarshal(tt.args.bidrequest, bidrequest) + tt.args.payload.BidRequest = bidrequest + got, err := m.handleBeforeValidationHook(tt.args.ctx, tt.args.moduleCtx, tt.args.payload) + if tt.isRequestNotRejected { + assert.Equal(t, tt.want.Reject, got.Reject) + assert.Equal(t, tt.want.NbrCode, got.NbrCode) + assert.NotEmpty(t, tt.want.DebugMessages) + return + } + if (err != nil) != tt.wantErr { + assert.Equal(t, tt.wantErr, err != nil) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetSlotName(t *testing.T) { + type args struct { + tagId string + impExt *models.ImpExtension + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Slot_name_from_gpid", + args: args{ + tagId: "some-tagid", + impExt: &models.ImpExtension{ + GpId: "some-gpid", + }, + }, + want: "some-gpid", + }, + { + name: "Slot_name_from_tagid", + args: args{ + tagId: "some-tagid", + impExt: &models.ImpExtension{ + Data: openrtb_ext.ExtImpData{ + PbAdslot: "some-pbadslot", + }, + }, + }, + want: "some-tagid", + }, + { + name: "Slot_name_from_pbadslot", + args: args{ + tagId: "", + impExt: &models.ImpExtension{ + Data: openrtb_ext.ExtImpData{ + PbAdslot: "some-pbadslot", + }, + }, + }, + want: "some-pbadslot", + }, + { + name: "Slot_name_from_stored_request_id", + args: args{ + tagId: "", + impExt: &models.ImpExtension{ + Prebid: openrtb_ext.ExtImpPrebid{ + StoredRequest: &openrtb_ext.ExtStoredRequest{ + ID: "stored-req-id", + }, + }, + }, + }, + want: "stored-req-id", + }, + { + name: "imp_ext_nil_slot_name_from_tag_id", + args: args{ + tagId: "some-tagid", + impExt: nil, + }, + want: "some-tagid", + }, + { + name: "empty_slot_name", + args: args{ + tagId: "", + impExt: &models.ImpExtension{}, + }, + want: "", + }, + { + name: "all_level_information_is_present_slot_name_picked_by_preference", + args: args{ + tagId: "some-tagid", + impExt: &models.ImpExtension{ + GpId: "some-gpid", + Data: openrtb_ext.ExtImpData{ + PbAdslot: "some-pbadslot", + }, + Prebid: openrtb_ext.ExtImpPrebid{ + StoredRequest: &openrtb_ext.ExtStoredRequest{ + ID: "stored-req-id", + }, + }, + }, + }, + want: "some-gpid", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getSlotName(tt.args.tagId, tt.args.impExt) + assert.Equal(t, tt.want, got, tt.name) + }) + } +} + +func TestGetAdunitName(t *testing.T) { + type args struct { + tagId string + impExt *models.ImpExtension + } + tests := []struct { + name string + args args + want string + }{ + { + name: "adunit_from_adserver_slot", + args: args{ + tagId: "some-tagid", + impExt: &models.ImpExtension{ + Data: openrtb_ext.ExtImpData{ + PbAdslot: "some-pbadslot", + AdServer: &openrtb_ext.ExtImpDataAdServer{ + Name: models.GamAdServer, + AdSlot: "gam-unit", + }, + }, + }, + }, + want: "gam-unit", + }, + { + name: "adunit_from_pbadslot", + args: args{ + tagId: "some-tagid", + impExt: &models.ImpExtension{ + Data: openrtb_ext.ExtImpData{ + PbAdslot: "some-pbadslot", + AdServer: &openrtb_ext.ExtImpDataAdServer{ + Name: models.GamAdServer, + AdSlot: "", + }, + }, + }, + }, + want: "some-pbadslot", + }, + { + name: "adunit_from_pbadslot_when_gam_is_absent", + args: args{ + tagId: "some-tagid", + impExt: &models.ImpExtension{ + Data: openrtb_ext.ExtImpData{ + PbAdslot: "some-pbadslot", + AdServer: &openrtb_ext.ExtImpDataAdServer{ + Name: "freewheel", + AdSlot: "freewheel-unit", + }, + }, + }, + }, + want: "some-pbadslot", + }, + { + name: "adunit_from_TagId", + args: args{ + tagId: "some-tagid", + impExt: &models.ImpExtension{ + Data: openrtb_ext.ExtImpData{ + PbAdslot: "", + AdServer: &openrtb_ext.ExtImpDataAdServer{ + Name: models.GamAdServer, + AdSlot: "", + }, + }, + }, + }, + want: "some-tagid", + }, + { + name: "adunit_from_TagId_imp_ext_nil", + args: args{ + tagId: "some-tagid", + impExt: nil, + }, + want: "some-tagid", + }, + { + name: "adunit_from_TagId_imp_ext_nil", + args: args{ + tagId: "some-tagid", + impExt: &models.ImpExtension{}, + }, + want: "some-tagid", + }, + { + name: "all_level_information_is_present_adunit_name_picked_by_preference", + args: args{ + tagId: "some-tagid", + impExt: &models.ImpExtension{ + GpId: "some-gpid", + Data: openrtb_ext.ExtImpData{ + PbAdslot: "some-pbadslot", + AdServer: &openrtb_ext.ExtImpDataAdServer{ + Name: models.GamAdServer, + AdSlot: "gam-unit", + }, + }, + }, + }, + want: "gam-unit", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getAdunitName(tt.args.tagId, tt.args.impExt) + assert.Equal(t, tt.want, got, tt.name) + }) + } +} + +func TestGetTagID(t *testing.T) { + type args struct { + imp openrtb2.Imp + impExt *models.ImpExtension + } + tests := []struct { + name string + args args + want string + }{ + { + name: "tagId_not_found", + args: args{ + imp: openrtb2.Imp{}, + impExt: &models.ImpExtension{}, + }, + want: "", + }, + { + name: "tagId_present_in_gpid", + args: args{ + imp: openrtb2.Imp{}, + impExt: &models.ImpExtension{ + GpId: "/7578294/adunit1", + }, + }, + want: "/7578294/adunit1", + }, + { + name: "tagId_set_by_publisher_on_page", + args: args{ + imp: openrtb2.Imp{ + TagID: "/7578294/adunit1", + }, + impExt: &models.ImpExtension{}, + }, + want: "/7578294/adunit1", + }, + { + name: "tagId_present_in_pbadslot", + args: args{ + imp: openrtb2.Imp{}, + impExt: &models.ImpExtension{ + Data: openrtb_ext.ExtImpData{ + PbAdslot: "/7578294/adunit1", + }, + }, + }, + want: "/7578294/adunit1", + }, + { + name: "tagId_present_in_pbadslot_and_gpid", + args: args{ + imp: openrtb2.Imp{}, + impExt: &models.ImpExtension{ + GpId: "/7578294/adunit123", + Data: openrtb_ext.ExtImpData{ + PbAdslot: "/7578294/adunit", + }, + }, + }, + want: "/7578294/adunit123", + }, + { + name: "tagId_present_in_imp.TagId_and_gpid", + args: args{ + imp: openrtb2.Imp{ + TagID: "/7578294/adunit", + }, + impExt: &models.ImpExtension{ + GpId: "/7578294/adunit123", + }, + }, + want: "/7578294/adunit123", + }, + { + name: "tagId_present_in_imp.TagId_and_pbadslot", + args: args{ + imp: openrtb2.Imp{ + TagID: "/7578294/adunit123", + }, + impExt: &models.ImpExtension{ + Data: openrtb_ext.ExtImpData{ + PbAdslot: "/7578294/adunit", + }, + }, + }, + want: "/7578294/adunit123", + }, + { + name: "tagId_present_in_imp.TagId_and_pbadslot_and_gpid", + args: args{ + imp: openrtb2.Imp{ + TagID: "/7578294/adunit", + }, + impExt: &models.ImpExtension{ + GpId: "/7578294/adunit123", + Data: openrtb_ext.ExtImpData{ + PbAdslot: "/7578294/adunit12345", + }, + }, + }, + want: "/7578294/adunit123", + }, + { + name: "GpId_contains_'#'", + args: args{ + imp: openrtb2.Imp{ + TagID: "/7578294/adunit", + }, + impExt: &models.ImpExtension{ + GpId: "/43743431/DMDemo#Div1", + Data: openrtb_ext.ExtImpData{ + PbAdslot: "/7578294/adunit12345", + }, + }, + }, + want: "/43743431/DMDemo", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getTagID(tt.args.imp, tt.args.impExt); got != tt.want { + t.Errorf("getTagID() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/bidderparams/common.go b/modules/pubmatic/openwrap/bidderparams/common.go new file mode 100644 index 00000000000..81ea475d9f3 --- /dev/null +++ b/modules/pubmatic/openwrap/bidderparams/common.go @@ -0,0 +1,178 @@ +package bidderparams + +import ( + "fmt" + "regexp" + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +var ignoreKeys = map[string]bool{ + models.PARTNER_ACCOUNT_NAME: true, + models.ADAPTER_NAME: true, + models.ADAPTER_ID: true, + models.TIMEOUT: true, + models.KEY_GEN_PATTERN: true, + models.PREBID_PARTNER_NAME: true, + models.PROTOCOL: true, + models.SERVER_SIDE_FLAG: true, + models.LEVEL: true, + models.PARTNER_ID: true, + models.REVSHARE: true, + models.THROTTLE: true, + models.BidderCode: true, + models.IsAlias: true, +} + +func getSlotMeta(rctx models.RequestCtx, cache cache.Cache, bidRequest openrtb2.BidRequest, imp openrtb2.Imp, impExt models.ImpExtension, partnerID int) ([]string, map[string]models.SlotMapping, models.SlotMappingInfo, [][2]int64) { + var slotMap map[string]models.SlotMapping + var slotMappingInfo models.SlotMappingInfo + + //don't read mappings from cache in case of test=2 + if !(rctx.IsTestRequest == models.TestValueTwo && rctx.PartnerConfigMap[partnerID][models.BidderCode] == models.BidderPubMatic) { + slotMap = cache.GetMappingsFromCacheV25(rctx, partnerID) + if slotMap == nil { + return nil, nil, models.SlotMappingInfo{}, nil + } + slotMappingInfo = cache.GetSlotToHashValueMapFromCacheV25(rctx, partnerID) + if len(slotMappingInfo.OrderedSlotList) == 0 { + return nil, nil, models.SlotMappingInfo{}, nil + } + } + + var hw [][2]int64 + if imp.Banner != nil { + if imp.Banner.W != nil && imp.Banner.H != nil { + hw = append(hw, [2]int64{*imp.Banner.H, *imp.Banner.W}) + } + + for _, format := range imp.Banner.Format { + hw = append(hw, [2]int64{format.H, format.W}) + } + } + + if imp.Video != nil { + hw = append(hw, [2]int64{0, 0}) + } + + if imp.Native != nil { + hw = append(hw, [2]int64{1, 1}) + } + + kgp := rctx.PartnerConfigMap[partnerID][models.KEY_GEN_PATTERN] + + var div string + if impExt.Wrapper != nil { + div = impExt.Wrapper.Div + } + + var slots []string + for _, format := range hw { + // TODO fix the param sequence. make it consistent. HxW + slot := models.GenerateSlotName(format[0], format[1], kgp, imp.TagID, div, rctx.Source) + if slot != "" { + slots = append(slots, slot) + // NYC_TODO: break at i=0 for pubmatic? + } + } + + // NYC_TODO wh is returned temporarily + return slots, slotMap, slotMappingInfo, hw +} + +/* +formSlotForDefaultMapping: In this method, we are removing wxh from the kgp because +pubmatic adapter sets wxh that we send in imp.ext.pubmatic.adslot as primary size while calling translator. +In case of default mappings, since all sizes are unmapped, we don't want to treat any size as primary +thats why we are removing size from kgp +*/ +func getDefaultMappingKGP(keyGenPattern string) string { + if strings.Contains(keyGenPattern, "@_W_x_H_") { + return strings.ReplaceAll(keyGenPattern, "@_W_x_H_", "") + } + return keyGenPattern +} + +// getSlotMappings will returns slotMapping from map based on slotKey +func getSlotMappings(matchedSlot, matchedPattern string, slotMap map[string]models.SlotMapping) map[string]interface{} { + slotKey := matchedSlot + if matchedPattern != "" { + slotKey = matchedPattern + } + + if slotMappingObj, ok := slotMap[strings.ToLower(slotKey)]; ok { + return slotMappingObj.SlotMappings + } + + return nil +} + +func GetMatchingSlot(rctx models.RequestCtx, cache cache.Cache, slot string, slotMap map[string]models.SlotMapping, slotMappingInfo models.SlotMappingInfo, isRegexKGP bool, partnerID int) (string, string) { + if _, ok := slotMap[strings.ToLower(slot)]; ok { + return slot, "" + } + + if isRegexKGP { + if matchedSlot, regexPattern := GetRegexMatchingSlot(rctx, cache, slot, slotMap, slotMappingInfo, partnerID); matchedSlot != "" { + return matchedSlot, regexPattern + } + } + + return "", "" +} + +const pubSlotRegex = "psregex_%d_%d_%d_%d_%s" // slot and its matching regex info at publisher, profile, display version and adapter level + +type regexSlotEntry struct { + SlotName string + RegexPattern string +} + +// TODO: handle this db injection correctly +func GetRegexMatchingSlot(rctx models.RequestCtx, cache cache.Cache, slot string, slotMap map[string]models.SlotMapping, slotMappingInfo models.SlotMappingInfo, partnerID int) (string, string) { + // Ex. "psregex_5890_56777_1_8_/43743431/DMDemo1@@728x90" + cacheKey := fmt.Sprintf(pubSlotRegex, rctx.PubID, rctx.ProfileID, rctx.DisplayID, partnerID, slot) + if v, ok := cache.Get(cacheKey); ok { + if rse, ok := v.(regexSlotEntry); ok { + return rse.SlotName, rse.RegexPattern + } + } + + //Flags passed to regexp.Compile + regexFlags := "(?i)" // case in-sensitive match + + // if matching regex is not found in cache, run checks for the regex patterns in DB + for _, slotname := range slotMappingInfo.OrderedSlotList { + slotnameMatched := false + dbSlotNameParts := strings.Split(slotname, "@") + requestSlotKeyParts := strings.Split(slot, "@") + if len(dbSlotNameParts) == len(requestSlotKeyParts) { + for i, dbPart := range dbSlotNameParts { + re, err := regexp.Compile(regexFlags + dbPart) + if err != nil { + // If an invalid regex pattern is encountered, check further entries intead of returning immediately + break + } + matchingPart := re.FindString(requestSlotKeyParts[i]) + if matchingPart == "" && requestSlotKeyParts[i] != "" { + // request slot key did not match the Regex pattern + // check the next regex pattern from the DB + break + } + if i == len(dbSlotNameParts)-1 { + slotnameMatched = true + } + } + } + + if slotnameMatched { + cache.Set(cacheKey, regexSlotEntry{SlotName: slot, RegexPattern: slotname}) + return slot, slotname + } + } + + return "", "" +} diff --git a/modules/pubmatic/openwrap/bidderparams/common_test.go b/modules/pubmatic/openwrap/bidderparams/common_test.go new file mode 100644 index 00000000000..a554f747690 --- /dev/null +++ b/modules/pubmatic/openwrap/bidderparams/common_test.go @@ -0,0 +1,615 @@ +package bidderparams + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + mock_cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/stretchr/testify/assert" +) + +func TestGetSlotMeta(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockCache := mock_cache.NewMockCache(ctrl) + + type args struct { + rctx models.RequestCtx + cache cache.Cache + bidRequest openrtb2.BidRequest + imp openrtb2.Imp + impExt models.ImpExtension + partnerID int + } + type want struct { + slots []string + slotMap map[string]models.SlotMapping + slotMappingInfo models.SlotMappingInfo + hw [][2]int64 + } + tests := []struct { + name string + args args + setup func() + want want + }{ + { + name: "Test_value_other_than_2_and_got_slot_map_empty_from_cache", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 1, + }, + cache: mockCache, + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(models.RequestCtx{ + IsTestRequest: 1, + }, 1).Return(nil) + }, + want: want{ + slots: nil, + slotMap: nil, + slotMappingInfo: models.SlotMappingInfo{}, + hw: nil, + }, + }, + { + name: "Test_value_other_than_2_and_got_slotMappingInfo_OrderedSlotList_empty_from_cache", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 1, + }, + cache: mockCache, + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(models.RequestCtx{ + IsTestRequest: 1, + }, 1).Return(map[string]models.SlotMapping{ + "test": { + PartnerId: 1, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(models.RequestCtx{ + IsTestRequest: 1, + }, 1).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{}, + }) + }, + want: want{ + slots: nil, + slotMap: nil, + slotMappingInfo: models.SlotMappingInfo{}, + hw: nil, + }, + }, + { + name: "Test_value_is_2_but_partner_is_other_than_pubmatic_got_slotMappingInfo_OrderedSlotList_empty_from_cache", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 2, + PartnerConfigMap: map[int]map[string]string{ + 2: { + "biddercode": "appnexus", + }, + }, + }, + cache: mockCache, + partnerID: 2, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(models.RequestCtx{ + IsTestRequest: 2, + PartnerConfigMap: map[int]map[string]string{ + 2: { + "biddercode": "appnexus", + }, + }, + }, 2).Return(map[string]models.SlotMapping{ + "test": { + PartnerId: 2, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(models.RequestCtx{ + IsTestRequest: 2, + PartnerConfigMap: map[int]map[string]string{ + 2: { + "biddercode": "appnexus", + }, + }, + }, 2).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{}, + }) + }, + want: want{ + slots: nil, + slotMap: nil, + slotMappingInfo: models.SlotMappingInfo{}, + hw: nil, + }, + }, + { + name: "Other_than_test_request_and_got_slot_map_and_slotMappingInfo_from_the_chche", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 0, + PartnerConfigMap: map[int]map[string]string{ + 1: { + "kgp": "_AU_", + }, + }, + }, + cache: mockCache, + partnerID: 1, + imp: getTestImp("/Test_Adunti1234", true, false), + impExt: models.ImpExtension{ + Wrapper: &models.ExtImpWrapper{ + Div: "Div1", + }, + }, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(models.RequestCtx{ + IsTestRequest: 0, + PartnerConfigMap: map[int]map[string]string{ + 1: { + "kgp": "_AU_", + }, + }, + }, 1).Return(map[string]models.SlotMapping{ + "test": { + PartnerId: 1, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(models.RequestCtx{ + IsTestRequest: 0, + PartnerConfigMap: map[int]map[string]string{ + 1: { + "kgp": "_AU_", + }, + }, + }, 1).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"test", "test1"}, + }) + }, + want: want{ + slots: []string{"/Test_Adunti1234", "/Test_Adunti1234"}, + slotMap: map[string]models.SlotMapping{ + "test": { + PartnerId: 1, + }, + }, + slotMappingInfo: models.SlotMappingInfo{ + OrderedSlotList: []string{"test", "test1"}, + }, + hw: [][2]int64{ + {300, 200}, + {500, 400}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + got, got1, got2, got3 := getSlotMeta(tt.args.rctx, tt.args.cache, tt.args.bidRequest, tt.args.imp, tt.args.impExt, tt.args.partnerID) + assert.Equal(t, tt.want.slots, got) + assert.Equal(t, tt.want.slotMap, got1) + assert.Equal(t, tt.want.slotMappingInfo, got2) + assert.Equal(t, tt.want.hw, got3) + }) + } +} + +func TestGetDefaultMappingKGP(t *testing.T) { + type args struct { + keyGenPattern string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "empty_keyGenPattern", + args: args{ + keyGenPattern: "", + }, + want: "", + }, + { + name: "keyGenPattern_contains_@_W_x_H_", + args: args{ + keyGenPattern: "_AU_@_W_x_H_", + }, + want: "_AU_", + }, + { + name: "keyGenPattern_contains_only_AU_", + args: args{ + keyGenPattern: "_AU_", + }, + want: "_AU_", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getDefaultMappingKGP(tt.args.keyGenPattern) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetSlotMappings(t *testing.T) { + type args struct { + matchedSlot string + matchedPattern string + slotMap map[string]models.SlotMapping + } + tests := []struct { + name string + args args + want map[string]interface{} + }{ + { + name: "found_matched_slot", + args: args{ + matchedSlot: "/Test_Adunit1234", + matchedPattern: "", + slotMap: map[string]models.SlotMapping{ + "/test_adunit1234": { + SlotMappings: map[string]interface{}{ + models.SITE_CACHE_KEY: "12313", + models.TAG_CACHE_KEY: "45343", + }, + }, + }, + }, + want: map[string]interface{}{ + models.SITE_CACHE_KEY: "12313", + models.TAG_CACHE_KEY: "45343", + }, + }, + { + name: "found_matched_pattern", + args: args{ + matchedSlot: "au123@div1@728x90", + matchedPattern: "au1.*@div.*@.*", + slotMap: map[string]models.SlotMapping{ + "au1.*@div.*@.*": { + SlotMappings: map[string]interface{}{ + models.SITE_CACHE_KEY: "12313", + models.TAG_CACHE_KEY: "45343", + }, + }, + }, + }, + want: map[string]interface{}{ + models.SITE_CACHE_KEY: "12313", + models.TAG_CACHE_KEY: "45343", + }, + }, + { + name: "not_found_matched_slot_as_well_as_matched_pattern", + args: args{ + matchedSlot: "", + matchedPattern: "", + slotMap: map[string]models.SlotMapping{}, + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getSlotMappings(tt.args.matchedSlot, tt.args.matchedPattern, tt.args.slotMap) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetMatchingSlot(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockCache := mock_cache.NewMockCache(ctrl) + + type args struct { + rctx models.RequestCtx + cache cache.Cache + slot string + slotMap map[string]models.SlotMapping + slotMappingInfo models.SlotMappingInfo + isRegexKGP bool + partnerID int + } + type want struct { + matchedSlot string + matchedPattern string + } + tests := []struct { + name string + args args + setup func() + want want + }{ + { + name: "Found_exact_match_slot", + args: args{ + slotMap: map[string]models.SlotMapping{ + "/test_adunit1234": { + PartnerId: 1, + AdapterId: 1, + VersionId: 1, + SlotName: "/Test_Adunit1234", + }, + }, + slot: "/Test_Adunit1234", + }, + want: want{ + matchedSlot: "/Test_Adunit1234", + matchedPattern: "", + }, + }, + { + name: "Not_found_exact_match_and_not_regex_as_well", + args: args{ + slotMap: map[string]models.SlotMapping{}, + isRegexKGP: false, + }, + want: want{ + matchedSlot: "", + matchedPattern: "", + }, + }, + { + name: "found_matced_regex_slot", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + }, + partnerID: 1, + slot: "AU123@Div1@728x90", + slotMappingInfo: models.SlotMappingInfo{ + OrderedSlotList: []string{"*", ".*@.*@.*"}, + HashValueMap: map[string]string{ + ".*@.*@.*": "2aa34b52a9e941c1594af7565e599c8d", // Code should match the given slot name with this regex + }, + }, + slotMap: map[string]models.SlotMapping{ + "AU123@Div1@728x90": { + SlotMappings: map[string]interface{}{ + "site": "123123", + "adtag": "45343", + }, + }, + }, + cache: mockCache, + isRegexKGP: true, + }, + setup: func() { + mockCache.EXPECT().Get("psregex_5890_123_1_1_AU123@Div1@728x90").Return(nil, false) + mockCache.EXPECT().Set("psregex_5890_123_1_1_AU123@Div1@728x90", regexSlotEntry{SlotName: "AU123@Div1@728x90", RegexPattern: ".*@.*@.*"}).Times(1) + }, + want: want{ + matchedSlot: "AU123@Div1@728x90", + matchedPattern: ".*@.*@.*", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + got, got1 := GetMatchingSlot(tt.args.rctx, tt.args.cache, tt.args.slot, tt.args.slotMap, tt.args.slotMappingInfo, tt.args.isRegexKGP, tt.args.partnerID) + assert.Equal(t, tt.want.matchedSlot, got) + assert.Equal(t, tt.want.matchedPattern, got1) + }) + } +} + +func TestGetRegexMatchingSlot(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + type args struct { + rctx models.RequestCtx + slot string + slotMap map[string]models.SlotMapping + slotMappingInfo models.SlotMappingInfo + partnerID int + } + type want struct { + matchedSlot string + regexPattern string + } + tests := []struct { + name string + args args + setup func() cache.Cache + want want + }{ + { + name: "happy_path_found_matched_regex_slot_entry_in_cahe", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + }, + partnerID: 1, + slot: "/Test_Adunit1234", + }, + setup: func() cache.Cache { + mockCache := mock_cache.NewMockCache(ctrl) + mockCache.EXPECT().Get("psregex_5890_123_1_1_/Test_Adunit1234").Return(interface{}(regexSlotEntry{SlotName: "/Test_Adunit1234", RegexPattern: "2aa34b52a9e941c1594af7565e599c8d"}), true) + return mockCache + }, + want: want{ + matchedSlot: "/Test_Adunit1234", + regexPattern: "2aa34b52a9e941c1594af7565e599c8d", + }, + }, + { + name: "not_found_matched_regex_slot_entry_in_cache", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + }, + partnerID: 1, + slot: "AU123@Div1@728x90", + slotMappingInfo: models.SlotMappingInfo{ + OrderedSlotList: []string{"AU1.*@Div.*@.*", ".*@.*@.*"}, + HashValueMap: map[string]string{ + "AU1.*@Div.*@.*": "2aa34b52a9e941c1594af7565e599c8d", + ".*@.*@.*": "2aa34b52a9e941c1594af7565e599c8d", + }, + }, + slotMap: map[string]models.SlotMapping{ + "AU123@Div1@728x90": { + SlotMappings: map[string]interface{}{ + "site": "123123", + "adtag": "45343", + }, + }, + }, + }, + setup: func() cache.Cache { + mockCache := mock_cache.NewMockCache(ctrl) + mockCache.EXPECT().Get("psregex_5890_123_1_1_AU123@Div1@728x90").Return(nil, false) + mockCache.EXPECT().Set("psregex_5890_123_1_1_AU123@Div1@728x90", regexSlotEntry{SlotName: "AU123@Div1@728x90", RegexPattern: "AU1.*@Div.*@.*"}).Times(1) + return mockCache + }, + want: want{ + matchedSlot: "AU123@Div1@728x90", + regexPattern: "AU1.*@Div.*@.*", + }, + }, + { + name: "not_found_matched_regex_slot_entry_in_cache_case_Insensitive_Adslot", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + }, + partnerID: 1, + slot: "au123@Div1@728x90", + slotMappingInfo: models.SlotMappingInfo{ + OrderedSlotList: []string{"AU1.*@Div.*@.*", ".*@.*@.*"}, + HashValueMap: map[string]string{ + "AU1.*@Div.*@.*": "2aa34b52a9e941c1594af7565e599c8d", + ".*@.*@.*": "2aa34b52a9e941c1594af7565e599c8d", + }, + }, + slotMap: map[string]models.SlotMapping{ + "au123@Div1@728x90": { + SlotMappings: map[string]interface{}{ + "site": "123123", + "adtag": "45343", + }, + }, + }, + }, + setup: func() cache.Cache { + mockCache := mock_cache.NewMockCache(ctrl) + mockCache.EXPECT().Get("psregex_5890_123_1_1_au123@Div1@728x90").Return(nil, false) + mockCache.EXPECT().Set("psregex_5890_123_1_1_au123@Div1@728x90", regexSlotEntry{SlotName: "au123@Div1@728x90", RegexPattern: "AU1.*@Div.*@.*"}).Times(1) + return mockCache + }, + want: want{ + matchedSlot: "au123@Div1@728x90", + regexPattern: "AU1.*@Div.*@.*", + }, + }, + { + name: "not_found_matched_regex_slot_entry_in_cache_cache_Incorrecct_regex", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + }, + partnerID: 1, + slot: "au123@Div1@728x90", + slotMappingInfo: models.SlotMappingInfo{ + OrderedSlotList: []string{"*@Div.*@*"}, + HashValueMap: map[string]string{ + "*@Div.*@*": "2aa34b52a9e941c1594af7565e599c8d", + }, + }, + slotMap: map[string]models.SlotMapping{ + "au123@Div1@728x90": { + SlotMappings: map[string]interface{}{ + "site": "123123", + "adtag": "45343", + }, + }, + }, + }, + setup: func() cache.Cache { + mockCache := mock_cache.NewMockCache(ctrl) + mockCache.EXPECT().Get("psregex_5890_123_1_1_au123@Div1@728x90").Return(nil, false) + return mockCache + }, + want: want{ + matchedSlot: "", + regexPattern: "", + }, + }, + { + name: "not_found_matched_regex_slot_entry_in_cache_cache_Invalid_regex_pattern", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + }, + partnerID: 1, + slot: "AU123@Div1@728x90", + slotMappingInfo: models.SlotMappingInfo{ + OrderedSlotList: []string{"*", ".*@.*@.*"}, + HashValueMap: map[string]string{ + "*": "2aa34b52a9e941c1594af7565e599c8d", // Invalid regex pattern + ".*@.*@.*": "2aa34b52a9e941c1594af7565e599c8d", // Code should match the given slot name with this regex + }, + }, + slotMap: map[string]models.SlotMapping{ + "AU123@Div1@728x90": { + SlotMappings: map[string]interface{}{ + "site": "123123", + "adtag": "45343", + }, + }, + }, + }, + setup: func() cache.Cache { + mockCache := mock_cache.NewMockCache(ctrl) + mockCache.EXPECT().Get("psregex_5890_123_1_1_AU123@Div1@728x90").Return(nil, false) + mockCache.EXPECT().Set("psregex_5890_123_1_1_AU123@Div1@728x90", regexSlotEntry{SlotName: "AU123@Div1@728x90", RegexPattern: ".*@.*@.*"}).Times(1) + return mockCache + }, + want: want{ + matchedSlot: "AU123@Div1@728x90", + regexPattern: ".*@.*@.*", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cache := tt.setup() + got, got1 := GetRegexMatchingSlot(tt.args.rctx, cache, tt.args.slot, tt.args.slotMap, tt.args.slotMappingInfo, tt.args.partnerID) + assert.Equal(t, tt.want.matchedSlot, got) + assert.Equal(t, tt.want.regexPattern, got1) + }) + } +} diff --git a/modules/pubmatic/openwrap/bidderparams/others.go b/modules/pubmatic/openwrap/bidderparams/others.go new file mode 100644 index 00000000000..3cf3b0729ae --- /dev/null +++ b/modules/pubmatic/openwrap/bidderparams/others.go @@ -0,0 +1,58 @@ +package bidderparams + +import ( + "errors" + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adapters" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +func PrepareAdapterParamsV25(rctx models.RequestCtx, cache cache.Cache, bidRequest openrtb2.BidRequest, imp openrtb2.Imp, impExt models.ImpExtension, partnerID int) (string, string, bool, []byte, error) { + partnerConfig, ok := rctx.PartnerConfigMap[partnerID] + if !ok { + return "", "", false, nil, errors.New("ErrBidderParamsValidationError") + } + + var isRegexSlot bool + var matchedSlot, matchedPattern string + + isRegexKGP := rctx.PartnerConfigMap[partnerID][models.KEY_GEN_PATTERN] == models.REGEX_KGP + slots, slotMap, slotMappingInfo, hw := getSlotMeta(rctx, cache, bidRequest, imp, impExt, partnerID) + + for i, slot := range slots { + matchedSlot, matchedPattern = GetMatchingSlot(rctx, cache, slot, slotMap, slotMappingInfo, isRegexKGP, partnerID) + if matchedSlot == "" { + continue + } + + slotMappingObj, ok := slotMap[strings.ToLower(matchedSlot)] + if !ok { + slotMappingObj = slotMap[strings.ToLower(matchedPattern)] + isRegexSlot = true + } + + bidderParams := make(map[string]interface{}, len(slotMappingObj.SlotMappings)) + for k, v := range slotMappingObj.SlotMappings { + bidderParams[k] = v + } + + for key, value := range partnerConfig { + if !ignoreKeys[key] { + bidderParams[key] = value + } + } + + h := hw[i][0] + w := hw[i][1] + params, err := adapters.PrepareBidParamJSONForPartner(&w, &h, bidderParams, slot, partnerConfig[models.PREBID_PARTNER_NAME], partnerConfig[models.BidderCode], &impExt) + if err != nil || params == nil { + continue + } + return matchedSlot, matchedPattern, isRegexSlot, params, nil + } + + return "", "", false, nil, nil +} diff --git a/modules/pubmatic/openwrap/bidderparams/pubmatic.go b/modules/pubmatic/openwrap/bidderparams/pubmatic.go new file mode 100644 index 00000000000..df02c250814 --- /dev/null +++ b/modules/pubmatic/openwrap/bidderparams/pubmatic.go @@ -0,0 +1,136 @@ +package bidderparams + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func PreparePubMaticParamsV25(rctx models.RequestCtx, cache cache.Cache, bidRequest openrtb2.BidRequest, imp openrtb2.Imp, impExt models.ImpExtension, partnerID int) (string, string, bool, []byte, error) { + wrapExt := fmt.Sprintf(`{"%s":%d,"%s":%d}`, models.SS_PM_VERSION_ID, rctx.DisplayID, models.SS_PM_PROFILE_ID, rctx.ProfileID) + + // change profile id for pubmatic2 + if secondaryProfileID, ok := rctx.PartnerConfigMap[partnerID][models.KEY_PROFILE_ID]; ok { + wrapExt = fmt.Sprintf(`{"%s":0,"%s":%s}`, models.SS_PM_VERSION_ID, models.SS_PM_PROFILE_ID, secondaryProfileID) + } + + extImpPubMatic := openrtb_ext.ExtImpPubmatic{ + PublisherId: strconv.Itoa(rctx.PubID), + WrapExt: json.RawMessage(wrapExt), + Keywords: getImpExtPubMaticKeyWords(impExt, rctx.PartnerConfigMap[partnerID][models.BidderCode]), + DealTier: getDealTier(impExt, rctx.PartnerConfigMap[partnerID][models.BidderCode]), + } + + slots, slotMap, slotMappingInfo, _ := getSlotMeta(rctx, cache, bidRequest, imp, impExt, partnerID) + + if rctx.IsTestRequest > 0 { + if len(slots) > 0 { + extImpPubMatic.AdSlot = slots[0] + } + params, err := json.Marshal(extImpPubMatic) + return extImpPubMatic.AdSlot, "", false, params, err + } + + hash := "" + var err error + var matchedSlot, matchedPattern string + isRegexSlot := false + + kgp := rctx.PartnerConfigMap[partnerID][models.KEY_GEN_PATTERN] + isRegexKGP := kgp == models.REGEX_KGP + + // simple+regex key match + for _, slot := range slots { + matchedSlot, matchedPattern = GetMatchingSlot(rctx, cache, slot, slotMap, slotMappingInfo, isRegexKGP, partnerID) + if matchedSlot != "" { + extImpPubMatic.AdSlot = matchedSlot + + if matchedPattern != "" { + isRegexSlot = true + // imp.TagID = hash + // TODO: handle kgpv case sensitivity in hashvaluemap + if slotMappingInfo.HashValueMap != nil { + if v, ok := slotMappingInfo.HashValueMap[matchedPattern]; ok { + extImpPubMatic.AdSlot = v + imp.TagID = hash // TODO, make imp pointer. But do other bidders accept hash as TagID? + } + } + } + + break + } + } + + if paramMap := getSlotMappings(matchedSlot, matchedPattern, slotMap); paramMap != nil { + if matchedPattern == "" { + // use alternate names defined in DB for this slot if selection is non-regex + // use owSlotName to addres case insensitive slotname. + // Ex: slot="/43743431/DMDEMO@300x250" and owSlotName="/43743431/DMDemo@300x250" + if v, ok := paramMap[models.KEY_OW_SLOT_NAME]; ok { + if owSlotName, ok := v.(string); ok { + extImpPubMatic.AdSlot = owSlotName + } + } + } + + // Update slot key for PubMatic secondary flow + if v, ok := paramMap[models.KEY_SLOT_NAME]; ok { + if secondarySlotName, ok := v.(string); ok { + extImpPubMatic.AdSlot = secondarySlotName + } + } + } + + // last resort: send slotname w/o size to translator + if extImpPubMatic.AdSlot == "" { + var div string + if impExt.Wrapper != nil { + div = impExt.Wrapper.Div + } + unmappedKPG := getDefaultMappingKGP(kgp) + extImpPubMatic.AdSlot = models.GenerateSlotName(0, 0, unmappedKPG, imp.TagID, div, rctx.Source) + if len(slots) != 0 { // reuse this field for wt and wl in combination with isRegex + matchedPattern = slots[0] + } + } + + params, err := json.Marshal(extImpPubMatic) + return matchedSlot, matchedPattern, isRegexSlot, params, err +} + +func getDealTier(impExt models.ImpExtension, bidderCode string) *openrtb_ext.DealTier { + if len(impExt.Bidder) != 0 { + if bidderExt, ok := impExt.Bidder[bidderCode]; ok && bidderExt != nil && bidderExt.DealTier != nil { + return bidderExt.DealTier + } + } + return nil +} + +func getImpExtPubMaticKeyWords(impExt models.ImpExtension, bidderCode string) []*openrtb_ext.ExtImpPubmaticKeyVal { + if len(impExt.Bidder) != 0 { + if bidderExt, ok := impExt.Bidder[bidderCode]; ok && bidderExt != nil && len(bidderExt.KeyWords) != 0 { + keywords := make([]*openrtb_ext.ExtImpPubmaticKeyVal, 0) + for _, keyVal := range bidderExt.KeyWords { + //ignore key values pair with no values + if len(keyVal.Values) == 0 { + continue + } + keyValPair := openrtb_ext.ExtImpPubmaticKeyVal{ + Key: keyVal.Key, + Values: keyVal.Values, + } + keywords = append(keywords, &keyValPair) + } + if len(keywords) != 0 { + return keywords + } + } + } + return nil +} diff --git a/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go b/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go new file mode 100644 index 00000000000..4edf1e4493f --- /dev/null +++ b/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go @@ -0,0 +1,877 @@ +package bidderparams + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + mock_cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func getTestImp(tagID string, banner bool, video bool) openrtb2.Imp { + if banner { + return openrtb2.Imp{ + ID: "111", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Format: []openrtb2.Format{ + { + W: 400, + H: 500, + }, + }, + }, + TagID: tagID, + } + } else if video { + return openrtb2.Imp{ + ID: "111", + Video: &openrtb2.Video{ + W: 200, + H: 300, + }, + TagID: tagID, + } + } + + return openrtb2.Imp{ + ID: "111", + Native: &openrtb2.Native{ + Request: "test", + Ver: "testVer", + }, + TagID: tagID, + } +} + +func TestGetImpExtPubMaticKeyWords(t *testing.T) { + type args struct { + impExt models.ImpExtension + bidderCode string + } + tests := []struct { + name string + args args + want []*openrtb_ext.ExtImpPubmaticKeyVal + }{ + { + name: "empty_impExt_bidder", + args: args{ + impExt: models.ImpExtension{ + Bidder: nil, + }, + }, + want: nil, + }, + { + name: "bidder_code_is_not_present_in_impExt_bidder", + args: args{ + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "appnexus": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + }, + bidderCode: "pubmatic", + }, + want: nil, + }, + { + name: "impExt_bidder_contains_key_value_pair_for_bidder_code", + args: args{ + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + }, + bidderCode: "pubmatic", + }, + want: []*openrtb_ext.ExtImpPubmaticKeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + { + name: "impExt_bidder_contains_key_value_pair_for_bidder_code_ignore_key_value_pair_with_no_values", + args: args{ + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{}, + }, + }, + }, + }, + }, + bidderCode: "pubmatic", + }, + want: []*openrtb_ext.ExtImpPubmaticKeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getImpExtPubMaticKeyWords(tt.args.impExt, tt.args.bidderCode) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetDealTier(t *testing.T) { + type args struct { + impExt models.ImpExtension + bidderCode string + } + tests := []struct { + name string + args args + want *openrtb_ext.DealTier + }{ + { + name: "impExt_bidder_is_empty", + args: args{ + impExt: models.ImpExtension{ + Bidder: nil, + }, + }, + want: nil, + }, + { + name: "bidder_code_is_not_present_in_impExt_bidder", + args: args{ + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "appnexus": { + DealTier: &openrtb_ext.DealTier{ + Prefix: "test", + MinDealTier: 10, + }, + }, + }, + }, + bidderCode: "pubmatic", + }, + want: nil, + }, + { + name: "bidder_code_is_present_in_impExt_bidder", + args: args{ + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + DealTier: &openrtb_ext.DealTier{ + Prefix: "test", + MinDealTier: 10, + }, + }, + }, + }, + bidderCode: "pubmatic", + }, + want: &openrtb_ext.DealTier{ + Prefix: "test", + MinDealTier: 10, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getDealTier(tt.args.impExt, tt.args.bidderCode) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestPreparePubMaticParamsV25(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockCache := mock_cache.NewMockCache(ctrl) + + type args struct { + rctx models.RequestCtx + cache cache.Cache + bidRequest openrtb2.BidRequest + imp openrtb2.Imp + impExt models.ImpExtension + partnerID int + } + type want struct { + matchedSlot string + matchedPattern string + isRegexSlot bool + params []byte + wantErr bool + } + tests := []struct { + name string + args args + setup func() + want want + }{ + { + name: "testRequest", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 1, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_AU_@_DIV_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + Wrapper: &models.ExtImpWrapper{ + Div: "Div1", + }, + }, + imp: getTestImp("/Test_Adunit1234", true, false), + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "test": { + PartnerId: 1, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"test", "test1"}, + }) + }, + want: want{ + matchedSlot: "/Test_Adunit1234@Div1@200x300", + matchedPattern: "", + isRegexSlot: false, + params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234@Div1@200x300","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}]}`), + wantErr: false, + }, + }, + { + name: "exact_matched_slot_found_adslot_updated_from_PubMatic_secondary_flow", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_AU_@_DIV_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + models.KEY_PROFILE_ID: "1323", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + Wrapper: &models.ExtImpWrapper{ + Div: "Div1", + }, + }, + imp: getTestImp("/Test_Adunit1234", true, false), + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "/test_adunit1234@div1@200x300": { + PartnerId: 1, + AdapterId: 1, + SlotName: "/Test_Adunit1234@Div1@200x300", + SlotMappings: map[string]interface{}{ + "site": "12313", + "adtag": "45343", + "slotName": "/Test_Adunit1234@DIV1@200x300", + }, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"test", "test1"}, + }) + }, + want: want{ + matchedSlot: "/Test_Adunit1234@Div1@200x300", + matchedPattern: "", + isRegexSlot: false, + params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234@DIV1@200x300","wrapper":{"version":0,"profile":1323},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}]}`), + wantErr: false, + }, + }, + { + name: "exact_matched_slot_found_adSlot_upadted_from_owSlotName", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_AU_@_DIV_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + Wrapper: &models.ExtImpWrapper{ + Div: "Div1", + }, + }, + imp: getTestImp("/Test_Adunit1234", true, false), + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "/test_adunit1234@div1@200x300": { + PartnerId: 1, + AdapterId: 1, + SlotName: "/Test_Adunit1234@Div1@200x300", + SlotMappings: map[string]interface{}{ + "site": "12313", + "adtag": "45343", + models.KEY_OW_SLOT_NAME: "/Test_Adunit1234@DIV1@200x300", + }, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"test", "test1"}, + }) + }, + want: want{ + matchedSlot: "/Test_Adunit1234@Div1@200x300", + matchedPattern: "", + isRegexSlot: false, + params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234@DIV1@200x300","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}]}`), + wantErr: false, + }, + }, + { + name: "regex_matched_slot_found_adSlot_upadted_from_hashValue", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_AU_@_DIV_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + Wrapper: &models.ExtImpWrapper{ + Div: "Div1", + }, + }, + imp: getTestImp("/Test_Adunit1234", true, false), + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + ".*@div.*@.*": { + PartnerId: 1, + AdapterId: 1, + SlotName: "/Test_Adunit1234@Div1@200x300", + SlotMappings: map[string]interface{}{ + "site": "12313", + "adtag": "45343", + }, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"test", "test1"}, + HashValueMap: map[string]string{ + ".*@Div.*@.*": "2aa34b52a9e941c1594af7565e599c8d", + }, + }) + mockCache.EXPECT().Get("psregex_5890_123_1_1_/Test_Adunit1234@Div1@200x300").Return(regexSlotEntry{ + SlotName: "/Test_Adunit1234@Div1@200x300", + RegexPattern: ".*@Div.*@.*", + }, true) + }, + want: want{ + matchedSlot: "/Test_Adunit1234@Div1@200x300", + matchedPattern: ".*@Div.*@.*", + isRegexSlot: true, + params: []byte(`{"publisherId":"5890","adSlot":"2aa34b52a9e941c1594af7565e599c8d","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}]}`), + wantErr: false, + }, + }, + { + name: "valid_pubmatic_native_params", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + Wrapper: &models.ExtImpWrapper{ + Div: "Div1", + }, + }, + imp: getTestImp("/Test_Adunit1234", false, false), + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "/test_adunit1234@1x1": { + PartnerId: 1, + AdapterId: 1, + SlotName: "/Test_Adunit1234@1x1", + SlotMappings: map[string]interface{}{ + "site": "12313", + "adtag": "45343", + models.KEY_OW_SLOT_NAME: "/Test_Adunit1234@1x1", + }, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"test", "test1"}, + }) + }, + want: want{ + matchedSlot: "/Test_Adunit1234@1x1", + matchedPattern: "", + isRegexSlot: false, + params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234@1x1","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}]}`), + wantErr: false, + }, + }, + { + name: "valid_pubmatic_video_params", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + Wrapper: &models.ExtImpWrapper{ + Div: "Div1", + }, + }, + imp: getTestImp("/Test_Adunit1234", false, true), + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "/test_adunit1234@0x0": { + PartnerId: 1, + AdapterId: 1, + SlotName: "/Test_Adunit1234@0x0", + SlotMappings: map[string]interface{}{ + "site": "12313", + "adtag": "45343", + models.KEY_OW_SLOT_NAME: "/Test_Adunit1234@0x0", + }, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"test", "test1"}, + }) + }, + want: want{ + matchedSlot: "/Test_Adunit1234@0x0", + matchedPattern: "", + isRegexSlot: false, + params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234@0x0","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}]}`), + wantErr: false, + }, + }, + { + name: "pubmatic_param_for_native_default", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + Wrapper: &models.ExtImpWrapper{ + Div: "Div1", + }, + }, + imp: getTestImp("/Test_Adunit1234", false, false), + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "random": { + SlotName: "/Test_Adunit1234", + SlotMappings: map[string]interface{}{ + models.SITE_CACHE_KEY: "12313", + models.TAG_CACHE_KEY: "45343", + }, + }, + }) + + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"random"}, + HashValueMap: map[string]string{ + "random": "2aa34b52a9e941c1594af7565e599c8d", + }, + }) + }, + want: want{ + matchedSlot: "", + matchedPattern: "/Test_Adunit1234@1x1", + isRegexSlot: false, + params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}]}`), + wantErr: false, + }, + }, + { + name: "pubmatic_param_for_banner_default", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + Wrapper: &models.ExtImpWrapper{ + Div: "Div1", + }, + }, + imp: getTestImp("/Test_Adunit1234", true, false), + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "random": { + SlotName: "/Test_Adunit1234", + SlotMappings: map[string]interface{}{ + models.SITE_CACHE_KEY: "12313", + models.TAG_CACHE_KEY: "45343", + }, + }, + }) + + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"random"}, + HashValueMap: map[string]string{ + "random": "2aa34b52a9e941c1594af7565e599c8d", + }, + }) + }, + want: want{ + matchedSlot: "", + matchedPattern: "/Test_Adunit1234@200x300", + isRegexSlot: false, + params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}]}`), + wantErr: false, + }, + }, + { + name: "pubmatic_param_for_video_default", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + Wrapper: &models.ExtImpWrapper{ + Div: "Div1", + }, + }, + imp: getTestImp("/Test_Adunit1234", false, true), + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "random": { + SlotName: "/Test_Adunit1234", + SlotMappings: map[string]interface{}{ + models.SITE_CACHE_KEY: "12313", + models.TAG_CACHE_KEY: "45343", + }, + }, + }) + + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"random"}, + HashValueMap: map[string]string{ + "random": "2aa34b52a9e941c1594af7565e599c8d", + }, + }) + }, + want: want{ + matchedSlot: "", + matchedPattern: "/Test_Adunit1234@0x0", + isRegexSlot: false, + params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}]}`), + wantErr: false, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + matchedSlot, matchedPattern, isRegexSlot, params, err := PreparePubMaticParamsV25(tt.args.rctx, tt.args.cache, tt.args.bidRequest, tt.args.imp, tt.args.impExt, tt.args.partnerID) + if (err != nil) != tt.want.wantErr { + assert.Equal(t, tt.want.wantErr, err != nil) + return + } + assert.Equal(t, tt.want.matchedSlot, matchedSlot) + assert.Equal(t, tt.want.matchedPattern, matchedPattern) + assert.Equal(t, tt.want.isRegexSlot, isRegexSlot) + assert.Equal(t, tt.want.params, params) + }) + } +} + +func createSlotMapping(slotName string, mappings map[string]interface{}) models.SlotMapping { + return models.SlotMapping{ + PartnerId: 0, + AdapterId: 0, + VersionId: 0, + SlotName: slotName, + SlotMappings: mappings, + Hash: "", + OrderID: 0, + } +} diff --git a/modules/pubmatic/openwrap/bidderparams/vast.go b/modules/pubmatic/openwrap/bidderparams/vast.go new file mode 100644 index 00000000000..c172b286f84 --- /dev/null +++ b/modules/pubmatic/openwrap/bidderparams/vast.go @@ -0,0 +1,174 @@ +package bidderparams + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adapters" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +func PrepareVASTBidderParams(rctx models.RequestCtx, cache cache.Cache, bidRequest openrtb2.BidRequest, imp openrtb2.Imp, impExt models.ImpExtension, partnerID int, adpodExt *models.AdPod) (string, json.RawMessage, []string, error) { + if imp.Video == nil { + return "", nil, nil, nil + } + + slots, slotMap, _, _ := getSlotMeta(rctx, cache, bidRequest, imp, impExt, partnerID) + if len(slots) == 0 { + return "", nil, nil, nil + } + + pubVASTTags := cache.GetPublisherVASTTagsFromCache(rctx.PubID) + if len(pubVASTTags) == 0 { + return "", nil, nil, nil + } + + matchedSlotKeys, err := getVASTBidderSlotKeys(&imp, slots[0], slotMap, pubVASTTags, adpodExt) + if len(matchedSlotKeys) == 0 { + return "", nil, nil, err + } + + bidParams := adapters.PrepareVASTBidderParamJSON(&bidRequest, &imp, pubVASTTags, matchedSlotKeys, slotMap, adpodExt) + + /* + Sample Values + //slotkey:/15671365/DMDemo1@com.pubmatic.openbid.app@ + //slotKeys:[/15671365/DMDemo1@com.pubmatic.openbid.app@101] + //slotMap:map[/15671365/DMDemo1@com.pubmatic.openbid.app@101:map[param1:6005 param2:test param3:example]] + //Ext:{"tags":[{"tagid":"101","url":"sample_url_1","dur":15,"price":"15","params":{"param1":"6005","param2":"test","param3":"example"}}]} + */ + return slots[0], bidParams, matchedSlotKeys, nil +} + +// getVASTBidderSlotKeys returns all slot keys which are matching to vast tag slot key +func getVASTBidderSlotKeys(imp *openrtb2.Imp, + slotKey string, + slotMap map[string]models.SlotMapping, + pubVASTTags models.PublisherVASTTags, + adpodExt *models.AdPod) ([]string, error) { + + //TODO: Optimize this function + var ( + result, defaultMapping []string + validationErr error + isValidationError bool + ) + + for _, sm := range slotMap { + //formCaseInsensitiveVASTSlotKey forms slotKey for case in-sensitive comparison. + //It converts AdUnit part of slot key to lower case for comparison. + //Currently it only supports slot-keys of the format @@ + //This function needs to be enhanced to support different slot-key formats. + key := formCaseInsensitiveVASTSlotKey(sm.SlotName) + tempSlotKey := formCaseInsensitiveVASTSlotKey(slotKey) + isDefaultMappingSelected := false + + index := strings.Index(key, "@@") + if index != -1 { + //prefix check only for `/15671365/MG_VideoAdUnit@` + if !strings.HasPrefix(tempSlotKey, key[:index+1]) { + continue + } + + //getting slot key `/15671365/MG_VideoAdUnit@@` + tempSlotKey = key[:index+2] + isDefaultMappingSelected = true + } else if !strings.HasPrefix(key, tempSlotKey) { + continue + } + + //get vast tag id and slotkey + vastTagID, _ := strconv.Atoi(key[len(tempSlotKey):]) + if vastTagID == 0 { + continue + } + + //check pubvasttag details + vastTag, ok := pubVASTTags[vastTagID] + if !ok { + continue + } + + //validate vast tag details + if err := validateVASTTag(vastTag, imp.Video.MinDuration, imp.Video.MaxDuration, adpodExt); nil != err { + isValidationError = true + continue + } + + if isDefaultMappingSelected { + defaultMapping = append(defaultMapping, sm.SlotName) + } else { + result = append(result, sm.SlotName) + } + } + + if len(result) == 0 && len(defaultMapping) == 0 && isValidationError { + validationErr = errors.New("ErrInvalidVastTag") + } + + if len(result) == 0 { + return defaultMapping, validationErr + } + + return result, validationErr +} + +// formCaseInsensitiveVASTSlotKey forms slotKey for case in-sensitive comparison. +// It converts AdUnit part of slot key to lower case for comparison. +// Currently it only supports slot-keys of the format @@ +// This function needs to be enhanced to support different slot-key formats. +func formCaseInsensitiveVASTSlotKey(key string) string { + index := strings.Index(key, "@") + caseInsensitiveSlotKey := key + if index != -1 { + caseInsensitiveSlotKey = strings.ToLower(key[:index]) + key[index:] + } + return caseInsensitiveSlotKey +} + +func validateVASTTag( + vastTag *models.VASTTag, + videoMinDuration, videoMaxDuration int64, + adpod *models.AdPod) error { + + if vastTag == nil { + return fmt.Errorf("Empty vast tag") + } + + //TODO: adding checks for Duration and URL + if len(vastTag.URL) == 0 { + return fmt.Errorf("VAST tag mandatory parameter 'url' missing: %v", vastTag.ID) + } + + if vastTag.Duration <= 0 { + return fmt.Errorf("VAST tag mandatory parameter 'duration' missing: %v", vastTag.ID) + } + + if videoMaxDuration != 0 && vastTag.Duration > int(videoMaxDuration) { + return fmt.Errorf("VAST tag 'duration' validation failed 'tag.duration > video.maxduration' vastTagID:%v, tag.duration:%v, video.maxduration:%v", vastTag.ID, vastTag.Duration, videoMaxDuration) + } + + if nil == adpod { + //non-adpod request + if videoMinDuration != 0 && vastTag.Duration < int(videoMinDuration) { + return fmt.Errorf("VAST tag 'duration' validation failed 'tag.duration < video.minduration' vastTagID:%v, tag.duration:%v, video.minduration:%v", vastTag.ID, vastTag.Duration, videoMinDuration) + } + + } else { + //adpod request + if nil != adpod.MinDuration && vastTag.Duration < *adpod.MinDuration { + return fmt.Errorf("VAST tag 'duration' validation failed 'tag.duration < adpod.minduration' vastTagID:%v, tag.duration:%v, adpod.minduration:%v", vastTag.ID, vastTag.Duration, *adpod.MinDuration) + } + + if nil != adpod.MaxDuration && vastTag.Duration > *adpod.MaxDuration { + return fmt.Errorf("VAST tag 'duration' validation failed 'tag.duration > adpod.maxduration' vastTagID:%v, tag.duration:%v, adpod.maxduration:%v", vastTag.ID, vastTag.Duration, *adpod.MaxDuration) + } + } + + return nil +} diff --git a/modules/pubmatic/openwrap/bidderparams/vast_test.go b/modules/pubmatic/openwrap/bidderparams/vast_test.go new file mode 100644 index 00000000000..63a55360177 --- /dev/null +++ b/modules/pubmatic/openwrap/bidderparams/vast_test.go @@ -0,0 +1,516 @@ +package bidderparams + +import ( + "fmt" + "sort" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestGetVASTBidderSlotKeys(t *testing.T) { + type args struct { + imp *openrtb2.Imp + slotKey string + slotMap map[string]models.SlotMapping + pubVASTTags models.PublisherVASTTags + adpodExt *models.AdPod + } + tests := []struct { + name string + args args + want []string + wantErr bool + }{ + { + name: `empty-imp-object`, + args: args{}, + want: nil, + }, + { + name: `non-video-request`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Banner: &openrtb2.Banner{}}, + }, + want: nil, + }, + { + name: `empty-mappings`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + }, + want: nil, + }, + { + name: `key-not-present`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + slotKey: `key-not-present@com.pubmatic.openbid.app@`, + slotMap: map[string]models.SlotMapping{ + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + }, + }, + want: nil, + }, + { + name: `invalid-vast-tag-id`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + slotKey: `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@`, + slotMap: map[string]models.SlotMapping{ + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@invalid-vast-tag-id": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@invalid-vast-tag-id", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + }, + }, + want: nil, + }, + { + name: `vast-tag-details-not-present`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + slotKey: `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@`, + slotMap: map[string]models.SlotMapping{ + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + }, + }, + want: nil, + }, + { + name: `invalid-vast-tag`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + slotKey: `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@`, + slotMap: map[string]models.SlotMapping{ + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + }, + pubVASTTags: models.PublisherVASTTags{ + 101: &models.VASTTag{}, + 102: &models.VASTTag{}, + }, + }, + want: nil, + wantErr: true, + }, + { + name: `valid-row-1`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + slotKey: `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@`, + slotMap: map[string]models.SlotMapping{ + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + }, + pubVASTTags: models.PublisherVASTTags{ + 101: &models.VASTTag{URL: `vast-tag-url-1`, Duration: 15}, + 102: &models.VASTTag{URL: `vast-tag-url-2`, Duration: 20}, + }, + }, + want: []string{ + `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101`, + `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102`, + }, + }, + { + name: `mixed-mappings`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + slotKey: `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@`, + slotMap: map[string]models.SlotMapping{ + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@101": createSlotMapping("/15671365/MG_VideoAdUnit@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@102": createSlotMapping("/15671365/MG_VideoAdUnit@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + }, + pubVASTTags: models.PublisherVASTTags{ + 101: &models.VASTTag{URL: `vast-tag-url-1`, Duration: 15}, + 102: &models.VASTTag{URL: `vast-tag-url-2`, Duration: 20}, + }, + }, + want: []string{ + `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101`, + `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102`, + }, + }, + { + name: `default-mappings-only`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + slotKey: `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@`, + slotMap: map[string]models.SlotMapping{ + "/15671365/MG_VideoAdUnit1@com.pubmatic.openbid.app@101": createSlotMapping("/15671365/MG_VideoAdUnit1@com.pubmatic.openbid.app@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit1@com.pubmatic.openbid.app@102": createSlotMapping("/15671365/MG_VideoAdUnit1@com.pubmatic.openbid.app@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@@101": createSlotMapping("/15671365/MG_VideoAdUnit@@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@@102": createSlotMapping("/15671365/MG_VideoAdUnit@@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + }, + pubVASTTags: models.PublisherVASTTags{ + 101: &models.VASTTag{URL: `vast-tag-url-1`, Duration: 15}, + 102: &models.VASTTag{URL: `vast-tag-url-2`, Duration: 20}, + }, + }, + want: []string{ + `/15671365/MG_VideoAdUnit@@101`, + `/15671365/MG_VideoAdUnit@@102`, + }, + }, + { + name: `no-site-bundle`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + slotKey: `/15671365/MG_VideoAdUnit@@`, + slotMap: map[string]models.SlotMapping{ + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@@101": createSlotMapping("/15671365/MG_VideoAdUnit@@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@@102": createSlotMapping("/15671365/MG_VideoAdUnit@@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + }, + pubVASTTags: models.PublisherVASTTags{ + 101: &models.VASTTag{URL: `vast-tag-url-1`, Duration: 15}, + 102: &models.VASTTag{URL: `vast-tag-url-2`, Duration: 20}, + }, + }, + want: []string{ + `/15671365/MG_VideoAdUnit@@101`, + `/15671365/MG_VideoAdUnit@@102`, + }, + }, + { + name: `different-site-bundle`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + slotKey: `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@`, + slotMap: map[string]models.SlotMapping{ + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@not-present-domain@102": createSlotMapping("/15671365/MG_VideoAdUnit@not-present-domain@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@@101": createSlotMapping("/15671365/MG_VideoAdUnit@@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@@102": createSlotMapping("/15671365/MG_VideoAdUnit@@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + }, + pubVASTTags: models.PublisherVASTTags{ + 101: &models.VASTTag{URL: `vast-tag-url-1`, Duration: 15}, + 102: &models.VASTTag{URL: `vast-tag-url-2`, Duration: 20}, + }, + }, + want: []string{ + `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101`, + }, + }, + { + name: `no-adunit`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + slotKey: `@@`, + slotMap: map[string]models.SlotMapping{ + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@101": createSlotMapping("/15671365/MG_VideoAdUnit@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@102": createSlotMapping("/15671365/MG_VideoAdUnit@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + }, + pubVASTTags: models.PublisherVASTTags{ + 101: &models.VASTTag{URL: `vast-tag-url-1`, Duration: 15}, + 102: &models.VASTTag{URL: `vast-tag-url-2`, Duration: 20}, + }, + }, + want: nil, + }, + { + name: `case in-sensitive Ad Unit mapping`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + slotKey: `/15671365/mg_VideoAdUnit@com.pubmatic.openbid.app@`, + slotMap: map[string]models.SlotMapping{ + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VIDEOAdUnit@com.pubmatic.openbid.app@102": createSlotMapping("/15671365/MG_VIDEOAdUnit@com.pubmatic.openbid.app@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@COM.pubmatic.openbid.app@103": createSlotMapping("/15671365/MG_VideoAdUnit@COM.pubmatic.openbid.app@103", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@101": createSlotMapping("/15671365/MG_VideoAdUnit@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@102": createSlotMapping("/15671365/MG_VideoAdUnit@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + }, + pubVASTTags: models.PublisherVASTTags{ + 101: &models.VASTTag{URL: `vast-tag-url-1`, Duration: 15}, + 102: &models.VASTTag{URL: `vast-tag-url-2`, Duration: 20}, + }, + }, + want: []string{ + `/15671365/MG_VIDEOAdUnit@com.pubmatic.openbid.app@102`, + `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101`, + }, + }, + { + name: `case sensitive site-bundle`, + args: args{ + imp: &openrtb2.Imp{ID: "123", Video: &openrtb2.Video{}}, + slotKey: `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@`, + slotMap: map[string]models.SlotMapping{ + "/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101": createSlotMapping("/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@not-present-domain@102": createSlotMapping("/15671365/MG_VideoAdUnit@not-present-domain@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@COM.pubmatic.openbid.app@103": createSlotMapping("/15671365/MG_VideoAdUnit@COM.pubmatic.openbid.app@103", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@@101": createSlotMapping("/15671365/MG_VideoAdUnit@@101", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + "/15671365/MG_VideoAdUnit@@102": createSlotMapping("/15671365/MG_VideoAdUnit@@102", + map[string]interface{}{"param1": "85394", "param2": "test", "param3": "example1"}), + }, + pubVASTTags: models.PublisherVASTTags{ + 101: &models.VASTTag{URL: `vast-tag-url-1`, Duration: 15}, + 102: &models.VASTTag{URL: `vast-tag-url-2`, Duration: 20}, + }, + }, + want: []string{ + `/15671365/MG_VideoAdUnit@com.pubmatic.openbid.app@101`, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getVASTBidderSlotKeys(tt.args.imp, tt.args.slotKey, tt.args.slotMap, tt.args.pubVASTTags, tt.args.adpodExt) + if (err != nil) != tt.wantErr { + assert.Equal(t, tt.wantErr, err != nil) + return + } + sort.Strings(got) + sort.Strings(tt.want) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestValidateVASTTag(t *testing.T) { + type args struct { + vastTag *models.VASTTag + videoMinDuration int64 + videoMaxDuration int64 + adpod *models.AdPod + } + tests := []struct { + name string + args args + wantErr error + }{ + { + name: `empty_vast_tag`, + args: args{}, + wantErr: fmt.Errorf("Empty vast tag"), + }, + { + name: `empty_url`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + }, + }, + wantErr: fmt.Errorf("VAST tag mandatory parameter 'url' missing: 101"), + }, + { + name: `empty_duration`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + URL: `vast_tag_url`, + }, + }, + wantErr: fmt.Errorf("VAST tag mandatory parameter 'duration' missing: 101"), + }, + { + name: `valid-without-duration-checks`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + URL: `vast_tag_url`, + Duration: 15, + }, + }, + wantErr: nil, + }, + { + name: `max_duration_check`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + URL: `vast_tag_url`, + Duration: 15, + }, + videoMaxDuration: 10, + }, + wantErr: fmt.Errorf("VAST tag 'duration' validation failed 'tag.duration > video.maxduration' vastTagID:101, tag.duration:15, video.maxduration:10"), + }, + { + name: `min_duration_check`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + URL: `vast_tag_url`, + Duration: 15, + }, + videoMaxDuration: 30, + videoMinDuration: 20, + }, + wantErr: fmt.Errorf("VAST tag 'duration' validation failed 'tag.duration < video.minduration' vastTagID:101, tag.duration:15, video.minduration:20"), + }, + { + name: `valid_non_adpod`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + URL: `vast_tag_url`, + Duration: 25, + }, + videoMaxDuration: 30, + videoMinDuration: 20, + }, + wantErr: nil, + }, + { + name: `valid_non_adpod_exact`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + URL: `vast_tag_url`, + Duration: 25, + }, + videoMaxDuration: 25, + videoMinDuration: 25, + }, + wantErr: nil, + }, + { + name: `empty_adpod`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + URL: `vast_tag_url`, + Duration: 25, + }, + videoMaxDuration: 25, + videoMinDuration: 25, + adpod: &models.AdPod{}, + }, + wantErr: nil, + }, + { + name: `adpod_min_duration_check`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + URL: `vast_tag_url`, + Duration: 5, + }, + videoMaxDuration: 25, + adpod: &models.AdPod{ + MinDuration: ptrutil.ToPtr(10), + }, + }, + wantErr: fmt.Errorf(`VAST tag 'duration' validation failed 'tag.duration < adpod.minduration' vastTagID:101, tag.duration:5, adpod.minduration:10`), + }, + { + name: `adpod_max_duration_check`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + URL: `vast_tag_url`, + Duration: 15, + }, + videoMaxDuration: 25, + adpod: &models.AdPod{ + MaxDuration: ptrutil.ToPtr(10), + }, + }, + wantErr: fmt.Errorf(`VAST tag 'duration' validation failed 'tag.duration > adpod.maxduration' vastTagID:101, tag.duration:15, adpod.maxduration:10`), + }, + { + name: `adpod_exact_duration_check`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + URL: `vast_tag_url`, + Duration: 15, + }, + videoMaxDuration: 25, + adpod: &models.AdPod{ + MinDuration: ptrutil.ToPtr(15), + MaxDuration: ptrutil.ToPtr(15), + }, + }, + wantErr: nil, + }, + { + name: `mixed-check-1`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + URL: `vast_tag_url`, + Duration: 15, + }, + videoMaxDuration: 25, + videoMinDuration: 5, + adpod: &models.AdPod{ + MinDuration: ptrutil.ToPtr(15), + MaxDuration: ptrutil.ToPtr(15), + }, + }, + wantErr: nil, + }, + { + name: `video-min-duration-check-skipped-incase-of-adpod`, + args: args{ + vastTag: &models.VASTTag{ + ID: 101, + URL: `vast_tag_url`, + Duration: 10, + }, + videoMaxDuration: 25, + videoMinDuration: 15, + adpod: &models.AdPod{ + MinDuration: ptrutil.ToPtr(10), + MaxDuration: ptrutil.ToPtr(10), + }, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateVASTTag(tt.args.vastTag, tt.args.videoMinDuration, tt.args.videoMaxDuration, tt.args.adpod); err != nil { + assert.EqualError(t, err, string(tt.wantErr.Error()), "Expected error:%v but got:%v", tt.wantErr, err) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/bidders.go b/modules/pubmatic/openwrap/bidders.go new file mode 100644 index 00000000000..17b70b53c8d --- /dev/null +++ b/modules/pubmatic/openwrap/bidders.go @@ -0,0 +1,21 @@ +package openwrap + +import ( + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +var alias = map[string]string{ + models.BidderAdGenerationAlias: string(openrtb_ext.BidderAdgeneration), + models.BidderDistrictmDMXAlias: string(openrtb_ext.BidderDmx), + models.BidderPubMaticSecondaryAlias: string(openrtb_ext.BidderPubmatic), + models.BidderDistrictmAlias: string(openrtb_ext.BidderAppnexus), + models.BidderAndBeyondAlias: string(openrtb_ext.BidderAdkernel), + models.BidderMediaFuseAlias: string(openrtb_ext.BidderAppnexus), +} + +// IsAlias will return copy of exisiting alias +func IsAlias(bidder string) (string, bool) { + v, ok := alias[bidder] + return v, ok +} diff --git a/modules/pubmatic/openwrap/cache/cache.go b/modules/pubmatic/openwrap/cache/cache.go new file mode 100644 index 00000000000..6458923d5db --- /dev/null +++ b/modules/pubmatic/openwrap/cache/cache.go @@ -0,0 +1,23 @@ +package cache + +import ( + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +type Cache interface { + GetPartnerConfigMap(pubid, profileid, displayversion int, endpoint string) (map[int]map[string]string, error) + GetAdunitConfigFromCache(request *openrtb2.BidRequest, pubID int, profileID, displayVersion int) *adunitconfig.AdUnitConfig + GetMappingsFromCacheV25(rctx models.RequestCtx, partnerID int) map[string]models.SlotMapping + GetSlotToHashValueMapFromCacheV25(rctx models.RequestCtx, partnerID int) models.SlotMappingInfo + GetPublisherVASTTagsFromCache(pubID int) models.PublisherVASTTags + + GetFSCDisabledPublishers() (map[int]struct{}, error) + GetFSCThresholdPerDSP() (map[int]int, error) + + GetTBFTrafficForPublishers() (map[int]map[int]int, error) + + Set(key string, value interface{}) + Get(key string) (interface{}, bool) +} diff --git a/modules/pubmatic/openwrap/cache/gocache/adunit_config.go b/modules/pubmatic/openwrap/cache/gocache/adunit_config.go new file mode 100644 index 00000000000..c540cb81bd7 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/adunit_config.go @@ -0,0 +1,44 @@ +package gocache + +import ( + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +func (c *cache) populateCacheWithAdunitConfig(pubID int, profileID, displayVersion int) (err error) { + adunitConfig, err := c.db.GetAdunitConfig(profileID, displayVersion) + if err != nil { + return err + } + + if adunitConfig != nil { + caseFoldConfigMap := make(map[string]*adunitconfig.AdConfig, len(adunitConfig.Config)) + for k, v := range adunitConfig.Config { + v.UniversalPixel = validUPixels(v.UniversalPixel) + caseFoldConfigMap[strings.ToLower(k)] = v + } + adunitConfig.Config = caseFoldConfigMap + } + + cacheKey := key(PubAdunitConfig, pubID, profileID, displayVersion) + c.cache.Set(cacheKey, adunitConfig, getSeconds(c.cfg.CacheDefaultExpiry)) + return +} + +// GetAdunitConfigFromCache this function gets adunit config from cache for a given request +func (c *cache) GetAdunitConfigFromCache(request *openrtb2.BidRequest, pubID int, profileID, displayVersion int) *adunitconfig.AdUnitConfig { + if request.Test == 2 { + return nil + } + + cacheKey := key(PubAdunitConfig, pubID, profileID, displayVersion) + if obj, ok := c.cache.Get(cacheKey); ok { + if v, ok := obj.(*adunitconfig.AdUnitConfig); ok { + return v + } + } + + return nil +} diff --git a/modules/pubmatic/openwrap/cache/gocache/adunit_config_test.go b/modules/pubmatic/openwrap/cache/gocache/adunit_config_test.go new file mode 100644 index 00000000000..27e9233b150 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/adunit_config_test.go @@ -0,0 +1,406 @@ +package gocache + +import ( + "fmt" + "strings" + "sync" + "testing" + "time" + + "github.com/golang/mock/gomock" + gocache "github.com/patrickmn/go-cache" + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database" + mock_database "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +var testAdunitConfig = &adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Regex: true, + Config: map[string]*adunitconfig.AdConfig{ + "default": { + Floors: &openrtb_ext.PriceFloorRules{ + FloorMin: 15, + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + Schema: openrtb_ext.PriceFloorSchema{ + Delimiter: "|", + Fields: strings.Fields("mediaType size domain"), + }, + Default: 5, + Values: map[string]float64{ + "banner|300x600|*": 4, + "banner|300x250|www.website.com": 1, + "banner|728x90|www.website.com": 5, + "*|728x90|www.website.com": 13, + }, + Currency: "USD", + ModelWeight: ptrutil.ToPtr(40), + ModelVersion: "model 1 from adunit config slot level", + }, + }, + Currency: "USD", + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + EnforceRate: 100, + EnforceJS: ptrutil.ToPtr(true), + }, + Enabled: ptrutil.ToPtr(true), + }, + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + ConnectionType: []int{2}, + Video: openrtb2.Video{ + MinDuration: 10, + MaxDuration: 50, + BAttr: []adcom1.CreativeAttribute{ + 6, + 7, + }, + Skip: ptrutil.ToPtr[int8](1), + SkipMin: 10, + SkipAfter: 15, + }, + }, + }, + UniversalPixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "pixle", + PixelType: "js", + Pos: "above", + MediaType: "banner", + Partners: []string{ + "pubmatic", + "appnexus", + }, + }, + }, + }, + "Div1": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + ConnectionType: []int{0, 1, 2, 4}, + Video: openrtb2.Video{ + MinDuration: 10, + MaxDuration: 50, + BAttr: []adcom1.CreativeAttribute{ + 6, + 7, + }, + Skip: ptrutil.ToPtr[int8](1), + SkipMin: 10, + SkipAfter: 15, + }, + }, + }, + Banner: &adunitconfig.Banner{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.BannerConfig{ + Banner: openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 200, + H: 300, + }, + { + W: 500, + H: 800, + }, + }, + }, + }, + }, + }, + "Div2": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + ConnectionType: []int{0, 1, 2, 4}, + Video: openrtb2.Video{ + MinDuration: 10, + MaxDuration: 50, + BAttr: []adcom1.CreativeAttribute{ + 6, + 7, + }, + Skip: ptrutil.ToPtr[int8](1), + SkipMin: 10, + SkipAfter: 15, + }, + }, + }, + }, + }, +} + +func Test_cache_populateCacheWithAdunitConfig(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDatabase := mock_database.NewMockDatabase(ctrl) + + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + type args struct { + pubID int + profileID int + displayVersion int + } + type want struct { + err error + adunitConfig *adunitconfig.AdUnitConfig + cacheEntry bool + } + tests := []struct { + name string + fields fields + args args + setup func() + want want + }{ + { + name: "error_in_returning_adunitconfig_from_the_DB", + fields: fields{ + cache: gocache.New(10, 10), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + pubID: testPubID, + profileID: testProfileID, + displayVersion: testVersionID, + }, + setup: func() { + mockDatabase.EXPECT().GetAdunitConfig(testProfileID, testVersionID).Return(nil, fmt.Errorf("Invalid json")) + }, + want: want{ + err: fmt.Errorf("Invalid json"), + adunitConfig: nil, + cacheEntry: false, + }, + }, + { + name: "valid_adunit_config", + fields: fields{ + cache: gocache.New(10, 10), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + pubID: testPubID, + profileID: testProfileID, + displayVersion: testVersionID, + }, + setup: func() { + mockDatabase.EXPECT().GetAdunitConfig(testProfileID, testVersionID).Return(testAdunitConfig, nil) + }, + want: want{ + err: nil, + adunitConfig: testAdunitConfig, + cacheEntry: true, + }, + }, + { + name: "returned_nil_adunitconfig_from_the_DB", + fields: fields{ + cache: gocache.New(10, 10), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + pubID: testPubID, + profileID: testProfileID, + displayVersion: testVersionID, + }, + setup: func() { + mockDatabase.EXPECT().GetAdunitConfig(testProfileID, testVersionID).Return(nil, nil) + }, + want: want{ + err: nil, + adunitConfig: nil, + cacheEntry: true, + }, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + } + err := c.populateCacheWithAdunitConfig(tt.args.pubID, tt.args.profileID, tt.args.displayVersion) + assert.Equal(t, tt.want.err, err) + cacheKey := key(PubAdunitConfig, tt.args.pubID, tt.args.profileID, tt.args.displayVersion) + adunitconfig, found := c.Get(cacheKey) + if tt.want.cacheEntry { + assert.True(t, found) + assert.Equal(t, tt.want.adunitConfig, adunitconfig) + } else { + assert.False(t, found) + assert.Nil(t, adunitconfig) + } + }) + } +} + +func Test_cache_GetAdunitConfigFromCache(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDatabase := mock_database.NewMockDatabase(ctrl) + newCache := gocache.New(10, 10) + + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + type args struct { + request *openrtb2.BidRequest + pubID int + profileID int + displayVersion int + } + type want struct { + adunitConfig *adunitconfig.AdUnitConfig + } + tests := []struct { + name string + fields fields + args args + want want + setup func() + }{ + { + name: "test_request", + fields: fields{ + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + request: &openrtb2.BidRequest{ + Test: 2, + }, + pubID: testPubID, + profileID: testProfileID, + displayVersion: 1, + }, + setup: func() {}, + want: want{ + adunitConfig: nil, + }, + }, + { + name: "successfully_get_value_from_cache", + fields: fields{ + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + request: &openrtb2.BidRequest{ + Test: 1, + }, + pubID: testPubID, + profileID: testProfileID, + displayVersion: 2, + }, + setup: func() { + cacheKey := key(PubAdunitConfig, testPubID, testProfileID, 2) + newCache.Set(cacheKey, testAdunitConfig, time.Duration(1)*time.Second) + }, + want: want{ + adunitConfig: testAdunitConfig, + }, + }, + { + name: "got_empty_adunitconfig_from_cache", + fields: fields{ + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + request: &openrtb2.BidRequest{ + Test: 1, + }, + pubID: testPubID, + profileID: testProfileID, + displayVersion: 3, + }, + setup: func() { + cacheKey := key(PubAdunitConfig, testPubID, testProfileID, 3) + newCache.Set(cacheKey, &adunitconfig.AdUnitConfig{}, time.Duration(1*time.Second)) + }, + want: want{ + adunitConfig: &adunitconfig.AdUnitConfig{}, + }, + }, + { + name: "cache_key_not_present_in_cache", + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + request: &openrtb2.BidRequest{ + Test: 1, + }, + pubID: testPubID, + profileID: testProfileID, + displayVersion: 4, + }, + setup: func() {}, + want: want{ + adunitConfig: nil, + }, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + c := &cache{ + cache: newCache, + cfg: tt.fields.cfg, + db: tt.fields.db, + } + adunitConfig := c.GetAdunitConfigFromCache(tt.args.request, tt.args.pubID, tt.args.profileID, tt.args.displayVersion) + assert.Equal(t, tt.want.adunitConfig, adunitConfig, "Expected: %v but got %v", tt.want.adunitConfig, adunitConfig) + }) + } +} diff --git a/modules/pubmatic/openwrap/cache/gocache/fullscreenclickability.go b/modules/pubmatic/openwrap/cache/gocache/fullscreenclickability.go new file mode 100644 index 00000000000..c2b34fc3d3c --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/fullscreenclickability.go @@ -0,0 +1,32 @@ +package gocache + +import ( + "fmt" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +var ( + errorFscPubMsg = "[ErrorFscPubUpdate]:%w" + errorFscDspMsg = "[ErrorFscDspUpdate]:%w" +) + +// Populates Cache with Fsc-Disabled Publishers +func (c *cache) GetFSCDisabledPublishers() (map[int]struct{}, error) { + fscDisabledPublishers, err := c.db.GetFSCDisabledPublishers() + if err != nil { + c.metricEngine.RecordDBQueryFailure(models.AllFscDisabledPublishersQuery, "", "") + return fscDisabledPublishers, fmt.Errorf(errorFscPubMsg, err) + } + return fscDisabledPublishers, nil +} + +// Populates cache with Fsc-Dsp Threshold Percentages +func (c *cache) GetFSCThresholdPerDSP() (map[int]int, error) { + fscThreshold, err := c.db.GetFSCThresholdPerDSP() + if err != nil { + c.metricEngine.RecordDBQueryFailure(models.AllDspFscPcntQuery, "", "") + return fscThreshold, fmt.Errorf(errorFscDspMsg, err) + } + return fscThreshold, nil +} diff --git a/modules/pubmatic/openwrap/cache/gocache/fullscreenclickability_test.go b/modules/pubmatic/openwrap/cache/gocache/fullscreenclickability_test.go new file mode 100644 index 00000000000..1bff06df94c --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/fullscreenclickability_test.go @@ -0,0 +1,176 @@ +package gocache + +import ( + "errors" + "sync" + "testing" + + "github.com/golang/mock/gomock" + gocache "github.com/patrickmn/go-cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database" + mock_database "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database/mock" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/stretchr/testify/assert" +) + +func TestGetFSCDisabledPublishers(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockDatabase := mock_database.NewMockDatabase(ctrl) + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + tests := []struct { + name string + want map[int]struct{} + wantErr bool + setup func() + fields fields + }{ + { + name: "Valid Data present in DB, return same", + want: map[int]struct{}{ + 5890: {}, + 5891: {}, + }, + setup: func() { + mockDatabase.EXPECT().GetFSCDisabledPublishers().Return(map[int]struct{}{ + 5890: {}, + 5891: {}, + }, nil) + }, + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + wantErr: false, + }, + { + name: "Error In DB, Set Empty", + want: map[int]struct{}{}, + setup: func() { + mockDatabase.EXPECT().GetFSCDisabledPublishers().Return(map[int]struct{}{}, errors.New("QUERY FAILED")) + mockEngine.EXPECT().RecordDBQueryFailure(models.AllFscDisabledPublishersQuery, "", "").Return() + }, + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + wantErr: true, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + metricEngine: mockEngine, + } + got, err := c.GetFSCDisabledPublishers() + if (err != nil) != tt.wantErr { + t.Errorf("mySqlDB.GetFSCDisabledPublishers() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + + }) + } +} + +func TestGetFSCThresholdPerDSP(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockDatabase := mock_database.NewMockDatabase(ctrl) + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + tests := []struct { + name string + want map[int]int + wantErr bool + setup func() + fields fields + }{ + { + name: "Valid Data present in DB, return same", + want: map[int]int{ + 6: 100, + 5: 45, + }, + setup: func() { + mockDatabase.EXPECT().GetFSCThresholdPerDSP().Return(map[int]int{ + 6: 100, + 5: 45, + }, nil) + }, + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + wantErr: false, + }, + { + name: "Error In DB, Set Empty", + want: map[int]int{}, + setup: func() { + mockDatabase.EXPECT().GetFSCThresholdPerDSP().Return(map[int]int{}, errors.New("QUERY FAILD")) + mockEngine.EXPECT().RecordDBQueryFailure(models.AllDspFscPcntQuery, "", "").Return() + }, + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + wantErr: true, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + metricEngine: mockEngine, + } + got, err := c.GetFSCThresholdPerDSP() + if (err != nil) != tt.wantErr { + t.Errorf("mySqlDB.GetFSCThresholdPerDSP() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + + }) + } +} diff --git a/modules/pubmatic/openwrap/cache/gocache/gocache.go b/modules/pubmatic/openwrap/cache/gocache/gocache.go new file mode 100644 index 00000000000..b327d0fcd34 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/gocache.go @@ -0,0 +1,66 @@ +package gocache + +import ( + "fmt" + "sync" + "time" + + gocache "github.com/patrickmn/go-cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" +) + +const ( + PUB_SLOT_INFO = "pslot_%d_%d_%d_%d" // publisher slot mapping at publisher, profile, display version and adapter level + PUB_HB_PARTNER = "hbplist_%d_%d_%d" // header bidding partner list at publishr,profile, display version level + //HB_PARTNER_CFG = "hbpcfg_%d" // header bidding partner configuration at partner level + //PubAadunitConfig - this key for storing adunit config at pub, profile and version level + PubAdunitConfig = "aucfg_%d_%d_%d" + PubSlotHashInfo = "pshash_%d_%d_%d_%d" // slot and its hash info at publisher, profile, display version and adapter level + PubSlotRegex = "psregex_%d_%d_%d_%d_%s" // slot and its matching regex info at publisher, profile, display version and adapter level + PubSlotNameHash = "pslotnamehash_%d" //publisher slotname hash mapping cache key + PubVASTTags = "pvasttags_%d" //publisher level vasttags +) + +func key(format string, v ...interface{}) string { + return fmt.Sprintf(format, v...) +} + +// NYC_TODO: refactor this to inject any kind of cache,replace cache with freecache library +// any db or cache should be injectable +type cache struct { + sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + metricEngine metrics.MetricsEngine +} + +var c *cache +var cOnce sync.Once + +func New(goCache *gocache.Cache, database database.Database, cfg config.Cache, metricEngine metrics.MetricsEngine) *cache { + cOnce.Do( + func() { + c = &cache{ + cache: goCache, + db: database, + cfg: cfg, + metricEngine: metricEngine, + } + }) + return c +} + +func getSeconds(duration int) time.Duration { + return time.Duration(duration) * time.Second +} + +func (c *cache) Set(key string, value interface{}) { + c.cache.Set(key, value, getSeconds(c.cfg.CacheDefaultExpiry)) +} + +func (c *cache) Get(key string) (interface{}, bool) { + return c.cache.Get(key) +} diff --git a/modules/pubmatic/openwrap/cache/gocache/gocache_test.go b/modules/pubmatic/openwrap/cache/gocache/gocache_test.go new file mode 100644 index 00000000000..277be698b5d --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/gocache_test.go @@ -0,0 +1,220 @@ +package gocache + +import ( + "sync" + "testing" + "time" + + "github.com/golang/mock/gomock" + gocache "github.com/patrickmn/go-cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database" + mock_database "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/stretchr/testify/assert" +) + +func Test_key(t *testing.T) { + type args struct { + format string + v []interface{} + } + tests := []struct { + name string + args args + want string + }{ + { + name: "get_key", + args: args{ + format: PUB_SLOT_INFO, + v: []interface{}{5890, 123, 1, 10}, + }, + want: "pslot_5890_123_1_10", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := key(tt.args.format, tt.args.v...); got != tt.want { + t.Errorf("key() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNew(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDatabase := mock_database.NewMockDatabase(ctrl) + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + + type args struct { + goCache *gocache.Cache + database database.Database + cfg config.Cache + metricsEngine metrics.MetricsEngine + } + tests := []struct { + name string + args args + }{ + { + name: "new_cache_instance", + args: args{ + goCache: gocache.New(100, 100), + database: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + metricsEngine: mockEngine, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cache := New(tt.args.goCache, tt.args.database, tt.args.cfg, tt.args.metricsEngine) + assert.NotNil(t, cache, "chache object should not be nl") + }) + } +} + +func Test_getSeconds(t *testing.T) { + type args struct { + duration int + } + tests := []struct { + name string + args args + want time.Duration + }{ + { + name: "get_to_seconds", + args: args{ + duration: 10, + }, + want: time.Duration(10000000000), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getSeconds(tt.args.duration); got != tt.want { + t.Errorf("getSeconds() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_cache_Set(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDatabase := mock_database.NewMockDatabase(ctrl) + + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + type args struct { + key string + value interface{} + } + tests := []struct { + name string + fields fields + args args + want string + }{ + { + name: "set_to_cache", + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + key: "test_key", + value: "test_value", + }, + want: "test_value", + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + } + c.Set(tt.args.key, tt.args.value) + value, found := c.Get(tt.args.key) + if !found { + t.Errorf("key should be present") + return + } + if value != tt.want { + t.Errorf("Expected= %v but got= %v", tt.want, value) + } + + }) + } +} + +func Test_cache_Get(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDatabase := mock_database.NewMockDatabase(ctrl) + + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + type args struct { + key string + } + tests := []struct { + name string + fields fields + args args + want interface{} + want1 bool + }{ + { + name: "get_from_cache", + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + key: "test_key", + }, + want: "test_value", + want1: true, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + } + c.Set(tt.args.key, "test_value") + got, got1 := c.Get(tt.args.key) + assert.Equal(t, tt.want, got) + if got1 != tt.want1 { + t.Errorf("cache.Get() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/cache/gocache/partner_config.go b/modules/pubmatic/openwrap/cache/gocache/partner_config.go new file mode 100644 index 00000000000..51579f20958 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/partner_config.go @@ -0,0 +1,99 @@ +package gocache + +import ( + "errors" + "fmt" + "strconv" + "time" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +// GetPartnerConfigMap returns partnerConfigMap using given parameters +func (c *cache) GetPartnerConfigMap(pubID, profileID, displayVersion int, endpoint string) (map[int]map[string]string, error) { + dbAccessed := false + var err error + startTime := time.Now() + + pubLockKey := key("%d", pubID) + if mapNameHash, ok := c.cache.Get(key(PubSlotNameHash, pubID)); !ok || mapNameHash == nil { + errPubSlotNameHash := c.LockAndLoad(pubLockKey, func() error { + dbAccessed = true + return c.populateCacheWithPubSlotNameHash(pubID) + }) + if errPubSlotNameHash != nil { + c.metricEngine.RecordDBQueryFailure(models.SlotNameHash, strconv.Itoa(pubID), strconv.Itoa(profileID)) + err = models.ErrorWrap(err, errPubSlotNameHash) + } + } + + if vastTags, ok := c.cache.Get(key(PubVASTTags, pubID)); !ok || vastTags == nil { + errPublisherVASTTag := c.LockAndLoad(pubLockKey, func() error { + dbAccessed = true + return c.populatePublisherVASTTags(pubID) + }) + if errPublisherVASTTag != nil { + c.metricEngine.RecordDBQueryFailure(models.PublisherVASTTagsQuery, strconv.Itoa(pubID), strconv.Itoa(profileID)) + err = models.ErrorWrap(err, errPublisherVASTTag) + } + } + + cacheKey := key(PUB_HB_PARTNER, pubID, profileID, displayVersion) + if obj, ok := c.cache.Get(cacheKey); ok && obj != nil { + return obj.(map[int]map[string]string), err + } + + lockKey := key("%d%d%d", pubID, profileID, displayVersion) + if errGetPartnerConfig := c.LockAndLoad(lockKey, func() error { + dbAccessed = true + return c.getActivePartnerConfigAndPopulateWrapperMappings(pubID, profileID, displayVersion) + }); errGetPartnerConfig != nil { + err = models.ErrorWrap(err, errGetPartnerConfig) + } + + var partnerConfigMap map[int]map[string]string + if obj, ok := c.cache.Get(cacheKey); ok && obj != nil { + partnerConfigMap = obj.(map[int]map[string]string) + } + + if dbAccessed { + c.metricEngine.RecordGetProfileDataTime(endpoint, strconv.Itoa(profileID), time.Since(startTime)) + } + return partnerConfigMap, err +} + +func (c *cache) getActivePartnerConfigAndPopulateWrapperMappings(pubID, profileID, displayVersion int) (err error) { + cacheKey := key(PUB_HB_PARTNER, pubID, profileID, displayVersion) + partnerConfigMap, err := c.db.GetActivePartnerConfigurations(pubID, profileID, displayVersion) + if err != nil { + c.metricEngine.RecordDBQueryFailure(models.PartnerConfigQuery, strconv.Itoa(pubID), strconv.Itoa(profileID)) + return + } + + if len(partnerConfigMap) == 0 { + return fmt.Errorf("there are no active partners for pubId:%d, profileId:%d, displayVersion:%d", pubID, profileID, displayVersion) + } + + c.cache.Set(cacheKey, partnerConfigMap, getSeconds(c.cfg.CacheDefaultExpiry)) + if errWrapperSlotMapping := c.populateCacheWithWrapperSlotMappings(pubID, partnerConfigMap, profileID, displayVersion); errWrapperSlotMapping != nil { + err = models.ErrorWrap(err, errWrapperSlotMapping) + queryType := models.WrapperSlotMappingsQuery + if displayVersion == 0 { + queryType = models.WrapperLiveVersionSlotMappings + } + c.metricEngine.RecordDBQueryFailure(queryType, strconv.Itoa(pubID), strconv.Itoa(profileID)) + } + if errAdunitConfig := c.populateCacheWithAdunitConfig(pubID, profileID, displayVersion); errAdunitConfig != nil { + queryType := models.AdunitConfigQuery + if displayVersion == 0 { + queryType = models.AdunitConfigForLiveVersion + } + if errors.Is(errAdunitConfig, adunitconfig.ErrAdUnitUnmarshal) { + queryType = models.AdUnitFailUnmarshal + } + c.metricEngine.RecordDBQueryFailure(queryType, strconv.Itoa(pubID), strconv.Itoa(profileID)) + err = models.ErrorWrap(err, errAdunitConfig) + } + return +} diff --git a/modules/pubmatic/openwrap/cache/gocache/partner_config_test.go b/modules/pubmatic/openwrap/cache/gocache/partner_config_test.go new file mode 100644 index 00000000000..464d1ce5ad5 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/partner_config_test.go @@ -0,0 +1,423 @@ +package gocache + +import ( + "fmt" + "sync" + "testing" + + "github.com/golang/mock/gomock" + gocache "github.com/patrickmn/go-cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database" + mock_database "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database/mock" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/stretchr/testify/assert" +) + +func Test_cache_GetPartnerConfigMap(t *testing.T) { + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + } + type args struct { + pubID int + profileID int + displayVersion int + endpoint string + } + tests := []struct { + name string + fields fields + args args + want map[int]map[string]string + wantErr bool + setup func(ctrl *gomock.Controller) (*mock_database.MockDatabase, *mock_metrics.MockMetricsEngine) + }{ + { + name: "get_valid_partnerConfig_map", + fields: fields{ + cache: gocache.New(100, 100), + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + VASTTagCacheExpiry: 1000, + }, + }, + args: args{ + pubID: testPubID, + profileID: testProfileID, + displayVersion: testVersionID, + endpoint: models.EndpointV25, + }, + setup: func(ctrl *gomock.Controller) (*mock_database.MockDatabase, *mock_metrics.MockMetricsEngine) { + mockDatabase := mock_database.NewMockDatabase(ctrl) + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockDatabase.EXPECT().GetActivePartnerConfigurations(testPubID, testProfileID, testVersionID).Return(formTestPartnerConfig(), nil) + mockDatabase.EXPECT().GetPublisherSlotNameHash(testPubID).Return(map[string]string{"adunit@728x90": "2aa34b52a9e941c1594af7565e599c8d"}, nil) + mockDatabase.EXPECT().GetPublisherVASTTags(testPubID).Return(nil, nil) + mockDatabase.EXPECT().GetAdunitConfig(testProfileID, testVersionID).Return(nil, nil) + mockDatabase.EXPECT().GetWrapperSlotMappings(formTestPartnerConfig(), testProfileID, testVersionID).Return(map[int][]models.SlotMapping{ + 1: { + { + PartnerId: testPartnerID, + AdapterId: testAdapterID, + VersionId: testVersionID, + SlotName: testSlotName, + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\",\"video\":{\"skippable\":\"TRUE\"}}", + }, + }, + }, nil) + mockEngine.EXPECT().RecordGetProfileDataTime(models.EndpointV25, "123", gomock.Any()).Return().Times(1) + return mockDatabase, mockEngine + }, + wantErr: false, + want: map[int]map[string]string{ + 1: { + "partnerId": "1", + "prebidPartnerName": "pubmatic", + "serverSideEnabled": "1", + "level": "multi", + "kgp": "_AU_@_W_x_H", + "timeout": "220", + "bidderCode": "pubmatic", + }, + }, + }, + { + name: "db_queries_failed_getting_partnerConfig_map", + fields: fields{ + cache: gocache.New(100, 100), + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + VASTTagCacheExpiry: 1000, + }, + }, + args: args{ + pubID: testPubID, + profileID: testProfileID, + displayVersion: testVersionID, + endpoint: models.EndpointV25, + }, + setup: func(ctrl *gomock.Controller) (*mock_database.MockDatabase, *mock_metrics.MockMetricsEngine) { + mockDatabase := mock_database.NewMockDatabase(ctrl) + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockDatabase.EXPECT().GetActivePartnerConfigurations(testPubID, testProfileID, testVersionID).Return(nil, fmt.Errorf("Error from the DB")) + mockDatabase.EXPECT().GetPublisherSlotNameHash(testPubID).Return(nil, fmt.Errorf("Error from the DB")) + mockDatabase.EXPECT().GetPublisherVASTTags(testPubID).Return(nil, fmt.Errorf("Error from the DB")) + mockEngine.EXPECT().RecordGetProfileDataTime(models.EndpointV25, "123", gomock.Any()).Return() + mockEngine.EXPECT().RecordDBQueryFailure(models.SlotNameHash, "5890", "123").Return() + mockEngine.EXPECT().RecordDBQueryFailure(models.PartnerConfigQuery, "5890", "123").Return() + mockEngine.EXPECT().RecordDBQueryFailure(models.PublisherVASTTagsQuery, "5890", "123").Return() + return mockDatabase, mockEngine + }, + wantErr: true, + want: nil, + }, + { + name: "db_queries_failed_getting_adunitconfig_and_wrapper_slotmappings", + fields: fields{ + cache: gocache.New(100, 100), + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + VASTTagCacheExpiry: 1000, + }, + }, + args: args{ + pubID: testPubID, + profileID: testProfileID, + displayVersion: 0, + endpoint: models.EndpointAMP, + }, + setup: func(ctrl *gomock.Controller) (*mock_database.MockDatabase, *mock_metrics.MockMetricsEngine) { + mockDatabase := mock_database.NewMockDatabase(ctrl) + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockDatabase.EXPECT().GetActivePartnerConfigurations(testPubID, testProfileID, 0).Return(formTestPartnerConfig(), nil) + mockDatabase.EXPECT().GetPublisherSlotNameHash(testPubID).Return(map[string]string{"adunit@728x90": "2aa34b52a9e941c1594af7565e599c8d"}, nil) + mockDatabase.EXPECT().GetPublisherVASTTags(testPubID).Return(nil, nil) + mockDatabase.EXPECT().GetAdunitConfig(testProfileID, 0).Return(nil, adunitconfig.ErrAdUnitUnmarshal) + mockDatabase.EXPECT().GetWrapperSlotMappings(formTestPartnerConfig(), testProfileID, 0).Return(nil, fmt.Errorf("Error from the DB")) + mockEngine.EXPECT().RecordGetProfileDataTime(models.EndpointAMP, "123", gomock.Any()).Return().Times(1) + mockEngine.EXPECT().RecordDBQueryFailure(models.AdUnitFailUnmarshal, "5890", "123").Return().Times(1) + mockEngine.EXPECT().RecordDBQueryFailure(models.WrapperLiveVersionSlotMappings, "5890", "123").Return().Times(1) + return mockDatabase, mockEngine + }, + wantErr: true, + want: map[int]map[string]string{ + 1: { + "partnerId": "1", + "prebidPartnerName": "pubmatic", + "serverSideEnabled": "1", + "level": "multi", + "kgp": "_AU_@_W_x_H", + "timeout": "220", + "bidderCode": "pubmatic", + }, + }, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + } + c.db, c.metricEngine = tt.setup(ctrl) + + got, err := c.GetPartnerConfigMap(tt.args.pubID, tt.args.profileID, tt.args.displayVersion, tt.args.endpoint) + if (err != nil) != tt.wantErr { + t.Errorf("cache.GetPartnerConfigMap() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_cache_GetPartnerConfigMap_LockandLoad(t *testing.T) { + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + } + type args struct { + pubID int + profileID int + displayVersion int + endpoint string + } + tests := []struct { + name string + fields fields + args args + want map[int]map[string]string + wantErr bool + setup func(ctrl *gomock.Controller) (*mock_database.MockDatabase, *mock_metrics.MockMetricsEngine) + }{ + { + name: "get_partnerConfig_map", + fields: fields{ + cache: gocache.New(100, 100), + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + VASTTagCacheExpiry: 1000, + }, + }, + args: args{ + pubID: testPubID, + profileID: testProfileID, + displayVersion: testVersionID, + endpoint: models.EndpointV25, + }, + setup: func(ctrl *gomock.Controller) (*mock_database.MockDatabase, *mock_metrics.MockMetricsEngine) { + mockDatabase := mock_database.NewMockDatabase(ctrl) + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockDatabase.EXPECT().GetActivePartnerConfigurations(testPubID, testProfileID, testVersionID).Return(formTestPartnerConfig(), nil) + mockDatabase.EXPECT().GetPublisherSlotNameHash(testPubID).Return(map[string]string{"adunit@728x90": "2aa34b52a9e941c1594af7565e599c8d"}, nil) + mockDatabase.EXPECT().GetPublisherVASTTags(testPubID).Return(nil, nil) + mockDatabase.EXPECT().GetAdunitConfig(testProfileID, testVersionID).Return(nil, nil) + mockDatabase.EXPECT().GetWrapperSlotMappings(formTestPartnerConfig(), testProfileID, testVersionID).Return(map[int][]models.SlotMapping{ + 1: { + { + PartnerId: testPartnerID, + AdapterId: testAdapterID, + VersionId: testVersionID, + SlotName: testSlotName, + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\",\"video\":{\"skippable\":\"TRUE\"}}", + }, + }, + }, nil) + mockEngine.EXPECT().RecordGetProfileDataTime(models.EndpointV25, "123", gomock.Any()).Return().Times(1) + return mockDatabase, mockEngine + }, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + } + c.db, c.metricEngine = tt.setup(ctrl) + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + c.GetPartnerConfigMap(tt.args.pubID, tt.args.profileID, tt.args.displayVersion, tt.args.endpoint) + wg.Done() + }() + } + wg.Wait() + cacheKey := key(PUB_HB_PARTNER, testPubID, testProfileID, testVersionID) + obj, found := c.Get(cacheKey) + if !found { + t.Error("Parner Config not added in cache") + return + } + + partnerConfigMap := obj.(map[int]map[string]string) + if _, found := partnerConfigMap[testAdapterID]; !found { + t.Error("Parner Config not added in map") + } + }) + } +} + +func Test_cache_getActivePartnerConfigAndPopulateWrapperMappings(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDatabase := mock_database.NewMockDatabase(ctrl) + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + type args struct { + pubID int + profileID int + displayVersion int + } + type want struct { + cacheEntry bool + err error + partnerConfigMap map[int]map[string]string + } + tests := []struct { + name string + fields fields + args args + want want + setup func() + }{ + { + name: "error_returning_Active_partner_configuration_from_DB", + fields: fields{ + cache: gocache.New(100, 100), + cfg: config.Cache{ + CacheDefaultExpiry: 100, + }, + db: mockDatabase, + }, + args: args{ + pubID: testPubID, + profileID: testProfileID, + displayVersion: testVersionID, + }, + want: want{ + cacheEntry: false, + err: fmt.Errorf("Error from the DB"), + partnerConfigMap: nil, + }, + setup: func() { + mockDatabase.EXPECT().GetActivePartnerConfigurations(testPubID, testProfileID, testVersionID).Return(nil, fmt.Errorf("Error from the DB")) + mockEngine.EXPECT().RecordDBQueryFailure(models.PartnerConfigQuery, "5890", "123").Return() + }, + }, + { + name: "non_nil_partnerConfigMap_from_DB", + fields: fields{ + cache: gocache.New(100, 100), + cfg: config.Cache{ + CacheDefaultExpiry: 100, + }, + db: mockDatabase, + }, + args: args{ + pubID: testPubID, + profileID: testProfileID, + displayVersion: testVersionID, + }, + want: want{ + cacheEntry: true, + err: nil, + partnerConfigMap: map[int]map[string]string{ + 1: { + "bidderCode": "pubmatic", + "kgp": "_AU_@_W_x_H", + "level": "multi", + "partnerId": "1", + "prebidPartnerName": "pubmatic", + "serverSideEnabled": "1", + "timeout": "220", + }, + }, + }, + setup: func() { + mockDatabase.EXPECT().GetActivePartnerConfigurations(testPubID, testProfileID, testVersionID).Return(formTestPartnerConfig(), nil) + mockDatabase.EXPECT().GetAdunitConfig(testProfileID, testVersionID).Return(nil, nil) + mockDatabase.EXPECT().GetWrapperSlotMappings(formTestPartnerConfig(), testProfileID, testVersionID).Return(map[int][]models.SlotMapping{ + 1: { + { + PartnerId: testPartnerID, + AdapterId: testAdapterID, + VersionId: testVersionID, + SlotName: testSlotName, + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\",\"video\":{\"skippable\":\"TRUE\"}}", + }, + }, + }, nil) + }, + }, + { + name: "empty_partnerConfigMap_from_DB", + fields: fields{ + cache: gocache.New(100, 100), + cfg: config.Cache{ + CacheDefaultExpiry: 100, + }, + db: mockDatabase, + }, + args: args{ + pubID: testPubID, + profileID: testProfileID, + displayVersion: testVersionID, + }, + want: want{ + cacheEntry: false, + err: fmt.Errorf("there are no active partners for pubId:%d, profileId:%d, displayVersion:%d", testPubID, testProfileID, testVersionID), + partnerConfigMap: nil, + }, + setup: func() { + mockDatabase.EXPECT().GetActivePartnerConfigurations(testPubID, testProfileID, testVersionID).Return(nil, nil) + }, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + metricEngine: mockEngine, + } + err := c.getActivePartnerConfigAndPopulateWrapperMappings(tt.args.pubID, tt.args.profileID, tt.args.displayVersion) + assert.Equal(t, tt.want.err, err) + cacheKey := key(PUB_HB_PARTNER, tt.args.pubID, tt.args.profileID, tt.args.displayVersion) + partnerConfigMap, found := c.Get(cacheKey) + if tt.want.cacheEntry { + assert.True(t, found) + assert.Equal(t, tt.want.partnerConfigMap, partnerConfigMap) + } else { + assert.False(t, found) + assert.Nil(t, partnerConfigMap) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/cache/gocache/slot_mappings.go b/modules/pubmatic/openwrap/cache/gocache/slot_mappings.go new file mode 100644 index 00000000000..b25246a7937 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/slot_mappings.go @@ -0,0 +1,106 @@ +package gocache + +import ( + "encoding/json" + "sort" + "strconv" + "strings" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// PopulateCacheWithPubSlotNameHash will put the slot names and hashes for a publisher in cache +func (c *cache) populateCacheWithPubSlotNameHash(pubID int) (err error) { + cacheKey := key(PubSlotNameHash, pubID) + + publisherSlotNameHashMap, err := c.db.GetPublisherSlotNameHash(pubID) + //This call may set nil publisherSlotNameHashMap in cache + c.cache.Set(cacheKey, publisherSlotNameHashMap, getSeconds(c.cfg.CacheDefaultExpiry)) + return +} + +// PopulateCacheWithWrapperSlotMappings will get the SlotMappings from database and put them in cache. +func (c *cache) populateCacheWithWrapperSlotMappings(pubID int, partnerConfigMap map[int]map[string]string, profileID, displayVersion int) error { + partnerSlotMappingMap, err := c.db.GetWrapperSlotMappings(partnerConfigMap, profileID, displayVersion) + + //put a version level dummy entry in cache denoting mappings are present for this version + cacheKey := key(PUB_SLOT_INFO, pubID, profileID, displayVersion, 0) + c.cache.Set(cacheKey, make(map[string]models.SlotMapping, 0), getSeconds(c.cfg.CacheDefaultExpiry)) + + if len(partnerSlotMappingMap) == 0 { + for _, partnerConf := range partnerConfigMap { + partnerID, _ := strconv.Atoi(partnerConf[models.PARTNER_ID]) + cacheKey = key(PUB_SLOT_INFO, pubID, profileID, displayVersion, partnerID) + c.cache.Set(cacheKey, make(map[string]models.SlotMapping, 0), getSeconds(c.cfg.CacheDefaultExpiry)) + } + return err + } + + var nameHashMap map[string]string + obj, ok := c.cache.Get(key(PubSlotNameHash, pubID)) + if ok && obj != nil { + nameHashMap = obj.(map[string]string) + } + + for partnerID, slotMappingList := range partnerSlotMappingMap { + slotNameToMappingMap := make(map[string]models.SlotMapping, len(slotMappingList)) + slotNameToHashValueMap := make(map[string]string, len(slotMappingList)) + slotNameOrderedList := make([]string, 0) + sort.Slice(slotMappingList, func(i, j int) bool { + return slotMappingList[i].OrderID < slotMappingList[j].OrderID + }) + for _, slotMapping := range slotMappingList { + slotMapping.Hash = nameHashMap[slotMapping.SlotName] + + var mappingJSONObj map[string]interface{} + if err := json.Unmarshal([]byte(slotMapping.MappingJson), &mappingJSONObj); err != nil { + continue + } + + cfgMap := partnerConfigMap[partnerID] + bidderCode := cfgMap[models.BidderCode] + if bidderCode == string(openrtb_ext.BidderPubmatic) { + //Adding slotName from DB in fieldMap for PubMatic, as slotName from DB should be sent to PubMatic instead of slotName from request + //This is required for case in-sensitive mapping + mappingJSONObj[models.KEY_OW_SLOT_NAME] = slotMapping.SlotName + } + + slotMapping.SlotMappings = mappingJSONObj + slotNameToMappingMap[strings.ToLower(slotMapping.SlotName)] = slotMapping + slotNameToHashValueMap[slotMapping.SlotName] = slotMapping.Hash + slotNameOrderedList = append(slotNameOrderedList, slotMapping.SlotName) + } + cacheKey = key(PUB_SLOT_INFO, pubID, profileID, displayVersion, partnerID) + c.cache.Set(cacheKey, slotNameToMappingMap, getSeconds(c.cfg.CacheDefaultExpiry)) + + slotMappingInfoObj := models.SlotMappingInfo{ + OrderedSlotList: slotNameOrderedList, + HashValueMap: slotNameToHashValueMap, + } + cacheKey = key(PubSlotHashInfo, pubID, profileID, displayVersion, partnerID) + c.cache.Set(cacheKey, slotMappingInfoObj, getSeconds(c.cfg.CacheDefaultExpiry)) + } + + return nil +} + +// GetMappingsFromCacheV25 will return mapping of each partner in partnerConf map +func (c *cache) GetMappingsFromCacheV25(rctx models.RequestCtx, partnerID int) map[string]models.SlotMapping { + cacheKey := key(PUB_SLOT_INFO, rctx.PubID, rctx.ProfileID, rctx.DisplayID, partnerID) + if value, ok := c.cache.Get(cacheKey); ok { + return value.(map[string]models.SlotMapping) + } + + return nil +} + +/*GetSlotToHashValueMapFromCacheV25 returns SlotMappingInfo object from cache that contains and ordered list of slot names by order_id field and a map of slot names to their hash values*/ +func (c *cache) GetSlotToHashValueMapFromCacheV25(rctx models.RequestCtx, partnerID int) models.SlotMappingInfo { + cacheKey := key(PubSlotHashInfo, rctx.PubID, rctx.ProfileID, rctx.DisplayID, partnerID) + if value, ok := c.cache.Get(cacheKey); ok { + return value.(models.SlotMappingInfo) + } + + return models.SlotMappingInfo{} +} diff --git a/modules/pubmatic/openwrap/cache/gocache/slot_mappings_test.go b/modules/pubmatic/openwrap/cache/gocache/slot_mappings_test.go new file mode 100644 index 00000000000..037cffbbb64 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/slot_mappings_test.go @@ -0,0 +1,579 @@ +package gocache + +import ( + "fmt" + "sync" + "testing" + "time" + + "github.com/golang/mock/gomock" + gocache "github.com/patrickmn/go-cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database" + mock_database "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/stretchr/testify/assert" +) + +const ( + testPubID = 5890 + testVersionID = 1 + testProfileID = 123 + testAdapterID = 1 + testPartnerID = 1 + testSlotName = "adunit@300x250" + testTimeout = 200 + testHashValue = "2aa34b52a9e941c1594af7565e599c8d" +) + +func Test_cache_populateCacheWithPubSlotNameHash(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDatabase := mock_database.NewMockDatabase(ctrl) + + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + type args struct { + pubid int + } + type want struct { + publisherSlotNameHashMap map[string]string + err error + } + tests := []struct { + name string + fields fields + args args + setup func() + want want + }{ + { + name: "returned_error_from_DB", + args: args{ + pubid: 5890, + }, + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 100, + }, + }, + setup: func() { + mockDatabase.EXPECT().GetPublisherSlotNameHash(5890).Return(nil, fmt.Errorf("Error from the DB")) + }, + want: want{ + publisherSlotNameHashMap: nil, + err: fmt.Errorf("Error from the DB"), + }, + }, + { + name: "returned_non_nil_PublisherSlotNameHash_from_DB", + args: args{ + pubid: 5890, + }, + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 100, + }, + }, + setup: func() { + mockDatabase.EXPECT().GetPublisherSlotNameHash(5890).Return(map[string]string{ + testSlotName: testHashValue, + }, nil) + }, + want: want{ + err: nil, + publisherSlotNameHashMap: map[string]string{ + testSlotName: testHashValue, + }, + }, + }, + { + name: "returned_nil_PublisherSlotNameHash_from_DB", + args: args{ + pubid: 5890, + }, + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 100, + }, + }, + setup: func() { + mockDatabase.EXPECT().GetPublisherSlotNameHash(5890).Return(nil, nil) + }, + want: want{ + publisherSlotNameHashMap: nil, + err: nil, + }, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + } + err := c.populateCacheWithPubSlotNameHash(tt.args.pubid) + assert.Equal(t, tt.want.err, err) + cacheKey := key(PubSlotNameHash, tt.args.pubid) + publisherSlotNameHashMap, found := c.cache.Get(cacheKey) + assert.True(t, found) + assert.Equal(t, tt.want.publisherSlotNameHashMap, publisherSlotNameHashMap) + }) + } +} + +func Test_cache_populateCacheWithWrapperSlotMappings(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDatabase := mock_database.NewMockDatabase(ctrl) + + newCache := gocache.New(10, 10) + + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + type args struct { + pubid int + partnerConfigMap map[int]map[string]string + profileId int + displayVersion int + } + type want struct { + partnerSlotMapping map[string]models.SlotMapping + err error + } + tests := []struct { + name string + fields fields + args args + setup func() + want want + }{ + { + name: "Error from the DB", + fields: fields{ + cache: newCache, + cfg: config.Cache{ + CacheDefaultExpiry: 100, + }, + db: mockDatabase, + }, + args: args{ + pubid: 58901, + partnerConfigMap: formTestPartnerConfig(), + profileId: testProfileID, + displayVersion: testVersionID, + }, + setup: func() { + mockDatabase.EXPECT().GetWrapperSlotMappings(formTestPartnerConfig(), testProfileID, testVersionID).Return(nil, fmt.Errorf("Error from the DB")) + }, + want: want{ + partnerSlotMapping: map[string]models.SlotMapping{}, + err: fmt.Errorf("Error from the DB"), + }, + }, + { + name: "empty_mappings", + fields: fields{ + cache: newCache, + cfg: config.Cache{ + CacheDefaultExpiry: 100, + }, + db: mockDatabase, + }, + args: args{ + pubid: 58902, + partnerConfigMap: formTestPartnerConfig(), + profileId: testProfileID, + displayVersion: testVersionID, + }, + setup: func() { + mockDatabase.EXPECT().GetWrapperSlotMappings(formTestPartnerConfig(), testProfileID, testVersionID).Return(nil, nil) + }, + want: want{ + partnerSlotMapping: map[string]models.SlotMapping{}, + err: nil, + }, + }, + { + name: "valid_mappings", + fields: fields{ + cache: newCache, + cfg: config.Cache{ + CacheDefaultExpiry: 100, + }, + db: mockDatabase, + }, + args: args{ + pubid: 58903, + partnerConfigMap: formTestPartnerConfig(), + profileId: testProfileID, + displayVersion: testVersionID, + }, + setup: func() { + mockDatabase.EXPECT().GetWrapperSlotMappings(formTestPartnerConfig(), testProfileID, testVersionID).Return(map[int][]models.SlotMapping{ + 1: { + { + PartnerId: testPartnerID, + AdapterId: testAdapterID, + VersionId: testVersionID, + SlotName: testSlotName, + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\",\"video\":{\"skippable\":\"TRUE\"}}", + }, + }, + }, nil) + }, + want: want{ + partnerSlotMapping: map[string]models.SlotMapping{ + "adunit@300x250": { + PartnerId: testPartnerID, + AdapterId: testAdapterID, + VersionId: testVersionID, + SlotName: testSlotName, + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\",\"video\":{\"skippable\":\"TRUE\"}}", + SlotMappings: map[string]interface{}{ + "adtag": "1405192", + "site": "47124", + "video": map[string]interface{}{ + "skippable": "TRUE", + }, + "owSlotName": "adunit@300x250", + }, + Hash: "", + OrderID: 0, + }, + }, + err: nil, + }, + }, + { + name: "HashValues", + fields: fields{ + cache: newCache, + cfg: config.Cache{ + CacheDefaultExpiry: 100, + }, + db: mockDatabase, + }, + args: args{ + pubid: 58904, + partnerConfigMap: formTestPartnerConfig(), + profileId: testProfileID, + displayVersion: testVersionID, + }, + setup: func() { + cacheKey := key(PubSlotNameHash, 58904) + newCache.Set(cacheKey, map[string]string{testSlotName: testHashValue}, time.Duration(1)*time.Second) + mockDatabase.EXPECT().GetWrapperSlotMappings(formTestPartnerConfig(), testProfileID, testVersionID).Return(map[int][]models.SlotMapping{ + 1: { + { + PartnerId: testPartnerID, + AdapterId: testAdapterID, + VersionId: testVersionID, + SlotName: testSlotName, + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\"}", + }, + }, + }, nil) + }, + want: want{ + err: nil, + partnerSlotMapping: map[string]models.SlotMapping{ + "adunit@300x250": { + PartnerId: testPartnerID, + AdapterId: testAdapterID, + VersionId: testVersionID, + SlotName: testSlotName, + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\"}", + SlotMappings: map[string]interface{}{ + "adtag": "1405192", + "site": "47124", + "owSlotName": "adunit@300x250", + }, + Hash: testHashValue, + OrderID: 0, + }, + }, + }, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + } + err := c.populateCacheWithWrapperSlotMappings(tt.args.pubid, tt.args.partnerConfigMap, tt.args.profileId, tt.args.displayVersion) + assert.Equal(t, tt.want.err, err) + + cacheKey := key(PUB_SLOT_INFO, tt.args.pubid, tt.args.profileId, tt.args.displayVersion, testPartnerID) + partnerSlotMapping, found := c.cache.Get(cacheKey) + assert.True(t, found) + assert.Equal(t, tt.want.partnerSlotMapping, partnerSlotMapping) + + }) + } +} + +func Test_cache_GetMappingsFromCacheV25(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDatabase := mock_database.NewMockDatabase(ctrl) + + newCache := gocache.New(10, 10) + + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + type args struct { + rctx models.RequestCtx + partnerID int + } + type want struct { + mappings map[string]models.SlotMapping + } + tests := []struct { + name string + fields fields + args args + want want + setup func() + }{ + { + name: "non_nil_partnerConf_map", + fields: fields{ + cache: newCache, + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + rctx: models.RequestCtx{ + PubID: testPubID, + ProfileID: testProfileID, + DisplayID: 1, + }, + partnerID: testAdapterID, + }, + setup: func() { + cacheKey := key(PUB_SLOT_INFO, testPubID, testProfileID, testVersionID, testAdapterID) + newCache.Set(cacheKey, map[string]models.SlotMapping{ + "adunit@300x250": { + PartnerId: 10, + AdapterId: 1, + VersionId: 1, + SlotName: "adunit@300x250", + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\"}", + SlotMappings: map[string]interface{}{ + "adtag": "1405192", + "site": "47124", + "owSlotName": "adunit@300x250", + }, + Hash: "", + OrderID: 0, + }, + }, time.Duration(1)*time.Second) + }, + want: want{ + mappings: map[string]models.SlotMapping{ + "adunit@300x250": { + PartnerId: 10, + AdapterId: 1, + VersionId: 1, + SlotName: "adunit@300x250", + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\"}", + SlotMappings: map[string]interface{}{ + "adtag": "1405192", + "site": "47124", + "owSlotName": "adunit@300x250", + }, + Hash: "", + OrderID: 0, + }, + }, + }, + }, + { + name: "nil_partnerConf_map", + fields: fields{ + cache: newCache, + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + rctx: models.RequestCtx{ + PubID: testPubID, + ProfileID: testProfileID, + DisplayID: 2, + }, + partnerID: 1, + }, + want: want{ + mappings: nil, + }, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + } + got := c.GetMappingsFromCacheV25(tt.args.rctx, tt.args.partnerID) + assert.Equal(t, tt.want.mappings, got) + }) + } +} + +func Test_cache_GetSlotToHashValueMapFromCacheV25(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDatabase := mock_database.NewMockDatabase(ctrl) + + newCache := gocache.New(10, 10) + + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + type args struct { + rctx models.RequestCtx + partnerID int + } + type want struct { + mappinInfo models.SlotMappingInfo + } + tests := []struct { + name string + fields fields + args args + want want + setup func() + }{ + { + name: "non_empty_SlotMappingInfo", + fields: fields{ + cache: newCache, + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + rctx: models.RequestCtx{ + PubID: testPubID, + ProfileID: testProfileID, + DisplayID: testVersionID, + }, + partnerID: 1, + }, + setup: func() { + cacheKey := key(PubSlotHashInfo, testPubID, testProfileID, testVersionID, testAdapterID) + newCache.Set(cacheKey, models.SlotMappingInfo{ + OrderedSlotList: []string{"adunit@300x250"}, + HashValueMap: map[string]string{ + "adunit@300x250": "2aa34b52a9e941c1594af7565e599c8d", + }, + }, time.Duration(1)*time.Second) + }, + want: want{ + mappinInfo: models.SlotMappingInfo{ + OrderedSlotList: []string{"adunit@300x250"}, + HashValueMap: map[string]string{ + "adunit@300x250": "2aa34b52a9e941c1594af7565e599c8d", + }, + }, + }, + }, + { + name: "empty_SlotMappingInfo", + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + }, + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + }, + partnerID: 1, + }, + want: want{ + mappinInfo: models.SlotMappingInfo{}, + }, + setup: func() {}, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + } + got := c.GetSlotToHashValueMapFromCacheV25(tt.args.rctx, tt.args.partnerID) + assert.Equal(t, tt.want.mappinInfo, got) + }) + } +} + +func formTestPartnerConfig() map[int]map[string]string { + + partnerConfigMap := make(map[int]map[string]string) + + partnerConfigMap[testAdapterID] = map[string]string{ + models.PARTNER_ID: "1", + models.PREBID_PARTNER_NAME: "pubmatic", + models.SERVER_SIDE_FLAG: "1", + models.LEVEL: "multi", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H", + models.TIMEOUT: "220", + models.BidderCode: "pubmatic", + } + + return partnerConfigMap +} diff --git a/modules/pubmatic/openwrap/cache/gocache/sync.go b/modules/pubmatic/openwrap/cache/gocache/sync.go new file mode 100644 index 00000000000..45f98ccd5f7 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/sync.go @@ -0,0 +1,19 @@ +package gocache + +// LockAndLoad calls DB only once for same requests +func (c *cache) LockAndLoad(key string, dbFunc func() error) (err error) { + waitCh := make(chan struct{}) + lockCh, present := c.LoadOrStore(key, waitCh) + if !present { + // fetch db data and save in cache + err = dbFunc() + // delete and let other requests take the lock (ideally only 1 per hour per pod) + c.Delete(key) + // unblock waiting requests + close(waitCh) + } + + // requests that did not get lock will wait here until the one that reterives the data closes the channel + <-lockCh.(chan struct{}) + return +} diff --git a/modules/pubmatic/openwrap/cache/gocache/sync_test.go b/modules/pubmatic/openwrap/cache/gocache/sync_test.go new file mode 100644 index 00000000000..82b6799501b --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/sync_test.go @@ -0,0 +1,89 @@ +package gocache + +import ( + "sync" + "testing" + "time" + + "github.com/golang/mock/gomock" + gocache "github.com/patrickmn/go-cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database" + mock_database "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database/mock" +) + +func Test_cache_LockAndLoad(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDatabase := mock_database.NewMockDatabase(ctrl) + + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + type args struct { + key string + dbFunc func() error + } + tests := []struct { + name string + fields fields + args args + wantErr bool + setup func() + }{ + { + name: "test", + fields: fields{ + cache: gocache.New(100, 100), + cfg: config.Cache{ + CacheDefaultExpiry: 1000, + }, + db: mockDatabase, + }, + args: args{ + key: "58901231", + }, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + } + tt.args.dbFunc = func() error { + c.cache.Set("test", "test", time.Duration(100*time.Second)) + return nil + } + var wg sync.WaitGroup + // var err error + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + c.LockAndLoad(tt.args.key, tt.args.dbFunc) + wg.Done() + }() + } + wg.Wait() + + obj, found := c.Get("test") + if !found { + t.Error("Parner Config not added in cache") + return + } + + value := obj.(string) + if value != "test" { + t.Error("Parner Config not added in map") + } + }) + } +} diff --git a/modules/pubmatic/openwrap/cache/gocache/tracking_beacon_first.go b/modules/pubmatic/openwrap/cache/gocache/tracking_beacon_first.go new file mode 100644 index 00000000000..fc128159916 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/tracking_beacon_first.go @@ -0,0 +1,12 @@ +// Package gocache contains caching functionalities of header-bidding repostiry +// This file provides caching functionalities for tracking-beacon-first (TBF) feature details +// associated with publishers. It includes methods to interact with the underlying database package +// for retrieving and caching publisher level TBF data. +package gocache + +// GetTBFTrafficForPublishers simply forwards the call to database +// This will not set the data in cache since TBF feature maintains its own map as cache +// Adding this function only because we are calling all database functions through cache +func (c *cache) GetTBFTrafficForPublishers() (pubProfileTraffic map[int]map[int]int, err error) { + return c.db.GetTBFTrafficForPublishers() +} diff --git a/modules/pubmatic/openwrap/cache/gocache/tracking_beacon_first_test.go b/modules/pubmatic/openwrap/cache/gocache/tracking_beacon_first_test.go new file mode 100644 index 00000000000..6c24353a6d9 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/tracking_beacon_first_test.go @@ -0,0 +1,42 @@ +package gocache + +import ( + "testing" + + mock_database "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database/mock" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestGetTBFTrafficForPublishers(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDatabase := mock_database.NewMockDatabase(ctrl) + + tests := []struct { + name string + setup func(dbCache *cache) + wantTrafficDetails map[int]map[int]int + wantErr error + }{ + { + name: "test_call_forwarding", + setup: func(dbCache *cache) { + mockDatabase.EXPECT().GetTBFTrafficForPublishers().Return(map[int]map[int]int{5890: {1234: 100}}, nil) + dbCache.db = mockDatabase + }, + wantTrafficDetails: map[int]map[int]int{5890: {1234: 100}}, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dbCache := cache{} + tt.setup(&dbCache) + actualTrafficDetails, err := dbCache.GetTBFTrafficForPublishers() + assert.Equal(t, actualTrafficDetails, tt.wantTrafficDetails, tt.name) + assert.Equal(t, tt.wantErr, err, tt.name) + }) + } +} diff --git a/modules/pubmatic/openwrap/cache/gocache/util.go b/modules/pubmatic/openwrap/cache/gocache/util.go new file mode 100644 index 00000000000..787c74f9a95 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/util.go @@ -0,0 +1,25 @@ +package gocache + +import ( + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +// validation check for Universal Pixels Object +func validUPixels(upixel []adunitconfig.UniversalPixel) []adunitconfig.UniversalPixel { + + var validPixels []adunitconfig.UniversalPixel + for index, pixel := range upixel { + if len(pixel.Pixel) == 0 { + continue + } + if len(pixel.PixelType) == 0 || (pixel.PixelType != models.PixelTypeJS && pixel.PixelType != models.PixelTypeUrl) { + continue + } + if pixel.Pos != "" && pixel.Pos != models.PixelPosAbove && pixel.Pos != models.PixelPosBelow { + continue + } + validPixels = append(validPixels, upixel[index]) + } + return validPixels +} diff --git a/modules/pubmatic/openwrap/cache/gocache/util_test.go b/modules/pubmatic/openwrap/cache/gocache/util_test.go new file mode 100644 index 00000000000..c6c274f6f66 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/util_test.go @@ -0,0 +1,176 @@ +package gocache + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/stretchr/testify/assert" +) + +func Test_validUPixels(t *testing.T) { + type args struct { + pixel []adunitconfig.UniversalPixel + } + tests := []struct { + name string + args args + want []adunitconfig.UniversalPixel + }{ + { + name: "No partners", + args: args{ + pixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: models.PixelTypeJS, + Pos: models.PixelPosAbove, + MediaType: "video", + }, + }, + }, + want: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: models.PixelTypeJS, + Pos: models.PixelPosAbove, + MediaType: "video", + }, + }, + }, + { + name: "No Pixel", + args: args{ + pixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + PixelType: models.PixelTypeJS, + Pos: models.PixelPosAbove, + MediaType: "video", + }, + }, + }, + want: nil, + }, + { + name: "Invalid Pixeltype", + args: args{ + pixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: "invalid", + Pos: models.PixelPosAbove, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + }, + }, + want: nil, + }, + { + name: "Pixeltype Not Present", + args: args{ + pixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + Pos: models.PixelPosAbove, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + }, + }, + want: nil, + }, + { + name: "Invalid Value of Pos and Other Valid Pixel", + args: args{ + pixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: models.PixelTypeJS, + Pos: "invalid", + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + { + Id: 123, + Pixel: "sample.com", + PixelType: models.PixelTypeJS, + Pos: models.PixelPosAbove, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + }, + }, + want: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: models.PixelTypeJS, + Pos: models.PixelPosAbove, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + }, + }, + { + name: "No Pos Value", + args: args{ + pixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: models.PixelTypeJS, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + }, + }, + want: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: models.PixelTypeJS, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + }, + }, + { + name: "Valid UPixel", + args: args{ + pixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: models.PixelTypeJS, + Pos: models.PixelPosAbove, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + }, + }, + want: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: models.PixelTypeJS, + Pos: models.PixelPosAbove, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := validUPixels(tt.args.pixel) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/cache/gocache/vast_tags.go b/modules/pubmatic/openwrap/cache/gocache/vast_tags.go new file mode 100644 index 00000000000..8f2137a03ec --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/vast_tags.go @@ -0,0 +1,33 @@ +package gocache + +import ( + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +// PopulatePublisherVASTTags will put publisher level VAST Tag details into cache +func (c *cache) populatePublisherVASTTags(pubID int) error { + cacheKey := key(PubVASTTags, pubID) + + //get publisher level vast tag details from DB + publisherVASTTags, err := c.db.GetPublisherVASTTags(pubID) + if err != nil { + return err + } + + if publisherVASTTags == nil { + publisherVASTTags = models.PublisherVASTTags{} + } + + c.cache.Set(cacheKey, publisherVASTTags, getSeconds(c.cfg.VASTTagCacheExpiry)) + return nil +} + +// GetPublisherVASTTagsFromCache read publisher level vast tag details from cache +func (c *cache) GetPublisherVASTTagsFromCache(pubID int) models.PublisherVASTTags { + cacheKey := key(PubVASTTags, pubID) + if value, ok := c.cache.Get(cacheKey); ok && value != nil { + return value.(models.PublisherVASTTags) + } + //if found then return actual value or else return empty + return models.PublisherVASTTags{} +} diff --git a/modules/pubmatic/openwrap/cache/gocache/vast_tags_test.go b/modules/pubmatic/openwrap/cache/gocache/vast_tags_test.go new file mode 100644 index 00000000000..3d529633551 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/gocache/vast_tags_test.go @@ -0,0 +1,226 @@ +package gocache + +import ( + "fmt" + "sync" + "testing" + + "github.com/golang/mock/gomock" + gocache "github.com/patrickmn/go-cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database" + mock_database "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/stretchr/testify/assert" +) + +func Test_cache_populatePublisherVASTTags(t *testing.T) { + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockDatabase := mock_database.NewMockDatabase(ctrl) + + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + type args struct { + pubid int + } + type want struct { + cacheEntry bool + err error + PublisherVASTTags models.PublisherVASTTags + } + tests := []struct { + name string + fields fields + args args + setup func() + want want + }{ + { + name: "error_in_returning_PublisherVASTTags_from_DB", + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + VASTTagCacheExpiry: 100000, + }, + }, + args: args{ + pubid: 5890, + }, + setup: func() { + mockDatabase.EXPECT().GetPublisherVASTTags(5890).Return(nil, fmt.Errorf("Error in returning PublisherVASTTags from the DB")) + }, + want: want{ + cacheEntry: false, + err: fmt.Errorf("Error in returning PublisherVASTTags from the DB"), + PublisherVASTTags: nil, + }, + }, + { + name: "successfully_got_PublisherVASTTags_from_DB_and_not_nil", + fields: fields{ + Map: sync.Map{}, + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + VASTTagCacheExpiry: 100000, + }, + }, + args: args{ + pubid: 5890, + }, + setup: func() { + mockDatabase.EXPECT().GetPublisherVASTTags(5890).Return(models.PublisherVASTTags{ + 101: {ID: 101, PartnerID: 501, URL: "vast_tag_url_1", Duration: 15, Price: 2.0}, + 102: {ID: 102, PartnerID: 502, URL: "vast_tag_url_2", Duration: 10, Price: 0.0}, + 103: {ID: 103, PartnerID: 501, URL: "vast_tag_url_1", Duration: 30, Price: 3.0}, + }, nil) + }, + want: want{ + cacheEntry: true, + err: nil, + PublisherVASTTags: map[int]*models.VASTTag{ + 101: {ID: 101, PartnerID: 501, URL: "vast_tag_url_1", Duration: 15, Price: 2.0}, + 102: {ID: 102, PartnerID: 502, URL: "vast_tag_url_2", Duration: 10, Price: 0.0}, + 103: {ID: 103, PartnerID: 501, URL: "vast_tag_url_1", Duration: 30, Price: 3.0}, + }, + }, + }, + { + name: "successfully_got_PublisherVASTTags_from_DB_but_it_is_nil", + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + VASTTagCacheExpiry: 100000, + }, + }, + args: args{ + pubid: 5890, + }, + setup: func() { + mockDatabase.EXPECT().GetPublisherVASTTags(5890).Return(nil, nil) + }, + want: want{ + cacheEntry: true, + err: nil, + PublisherVASTTags: models.PublisherVASTTags{}, + }, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + } + err := c.populatePublisherVASTTags(tt.args.pubid) + assert.Equal(t, tt.want.err, err) + + cacheKey := key(PubVASTTags, tt.args.pubid) + PublisherVASTTags, found := c.cache.Get(cacheKey) + if tt.want.cacheEntry { + assert.True(t, found) + assert.Equal(t, tt.want.PublisherVASTTags, PublisherVASTTags) + } else { + assert.False(t, found) + assert.Nil(t, PublisherVASTTags) + } + }) + } +} + +func Test_cache_GetPublisherVASTTagsFromCache(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockDatabase := mock_database.NewMockDatabase(ctrl) + type fields struct { + Map sync.Map + cache *gocache.Cache + cfg config.Cache + db database.Database + } + type args struct { + pubID int + } + tests := []struct { + name string + fields fields + args args + want models.PublisherVASTTags + setup func() + }{ + { + name: "Vast_Tags_found_in_cache_for_cache_key", + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + VASTTagCacheExpiry: 100000, + }, + }, + args: args{ + pubID: 5890, + }, + want: map[int]*models.VASTTag{ + 101: {ID: 101, PartnerID: 501, URL: "vast_tag_url_1", Duration: 15, Price: 2.0}, + 102: {ID: 102, PartnerID: 502, URL: "vast_tag_url_2", Duration: 10, Price: 0.0}, + 103: {ID: 103, PartnerID: 501, URL: "vast_tag_url_1", Duration: 30, Price: 3.0}, + }, + setup: func() { + mockDatabase.EXPECT().GetPublisherVASTTags(5890).Return(models.PublisherVASTTags{ + 101: {ID: 101, PartnerID: 501, URL: "vast_tag_url_1", Duration: 15, Price: 2.0}, + 102: {ID: 102, PartnerID: 502, URL: "vast_tag_url_2", Duration: 10, Price: 0.0}, + 103: {ID: 103, PartnerID: 501, URL: "vast_tag_url_1", Duration: 30, Price: 3.0}, + }, nil) + }, + }, + { + name: "Vast_Tags_not_found_in_cache_for_cache_key", + fields: fields{ + cache: gocache.New(100, 100), + db: mockDatabase, + cfg: config.Cache{ + VASTTagCacheExpiry: 100000, + }, + }, + args: args{ + pubID: 5890, + }, + want: models.PublisherVASTTags{}, + setup: func() { + mockDatabase.EXPECT().GetPublisherVASTTags(5890).Return(nil, fmt.Errorf("Error in returning PublisherVASTTags from the DB")) + }, + }, + } + for ind := range tests { + tt := &tests[ind] + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + c := &cache{ + cache: tt.fields.cache, + cfg: tt.fields.cfg, + db: tt.fields.db, + } + c.populatePublisherVASTTags(tt.args.pubID) + cacheKey := key(PubVASTTags, tt.args) + got := c.GetPublisherVASTTagsFromCache(tt.args.pubID) + assert.Equal(t, tt.want, got, "Vast tags for cacheKey= %v \n Expected= %v, but got= %v", cacheKey, got, tt.want) + }) + } +} diff --git a/modules/pubmatic/openwrap/cache/mock/mock.go b/modules/pubmatic/openwrap/cache/mock/mock.go new file mode 100644 index 00000000000..d499ee83790 --- /dev/null +++ b/modules/pubmatic/openwrap/cache/mock/mock.go @@ -0,0 +1,180 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/PubMatic-OpenWrap/prebid-server/modules/pubmatic/openwrap/cache (interfaces: Cache) + +// Package mock_cache is a generated GoMock package. +package mock_cache + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + openrtb2 "github.com/prebid/openrtb/v19/openrtb2" + models "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + adunitconfig "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +// MockCache is a mock of Cache interface. +type MockCache struct { + ctrl *gomock.Controller + recorder *MockCacheMockRecorder +} + +// MockCacheMockRecorder is the mock recorder for MockCache. +type MockCacheMockRecorder struct { + mock *MockCache +} + +// NewMockCache creates a new mock instance. +func NewMockCache(ctrl *gomock.Controller) *MockCache { + mock := &MockCache{ctrl: ctrl} + mock.recorder = &MockCacheMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCache) EXPECT() *MockCacheMockRecorder { + return m.recorder +} + +// Get mocks base method. +func (m *MockCache) Get(arg0 string) (interface{}, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockCacheMockRecorder) Get(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockCache)(nil).Get), arg0) +} + +// GetAdunitConfigFromCache mocks base method. +func (m *MockCache) GetAdunitConfigFromCache(arg0 *openrtb2.BidRequest, arg1, arg2, arg3 int) *adunitconfig.AdUnitConfig { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAdunitConfigFromCache", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*adunitconfig.AdUnitConfig) + return ret0 +} + +// GetAdunitConfigFromCache indicates an expected call of GetAdunitConfigFromCache. +func (mr *MockCacheMockRecorder) GetAdunitConfigFromCache(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAdunitConfigFromCache", reflect.TypeOf((*MockCache)(nil).GetAdunitConfigFromCache), arg0, arg1, arg2, arg3) +} + +// GetFSCDisabledPublishers mocks base method. +func (m *MockCache) GetFSCDisabledPublishers() (map[int]struct{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFSCDisabledPublishers") + ret0, _ := ret[0].(map[int]struct{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFSCDisabledPublishers indicates an expected call of GetFSCDisabledPublishers. +func (mr *MockCacheMockRecorder) GetFSCDisabledPublishers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFSCDisabledPublishers", reflect.TypeOf((*MockCache)(nil).GetFSCDisabledPublishers)) +} + +// GetFSCThresholdPerDSP mocks base method. +func (m *MockCache) GetFSCThresholdPerDSP() (map[int]int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFSCThresholdPerDSP") + ret0, _ := ret[0].(map[int]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFSCThresholdPerDSP indicates an expected call of GetFSCThresholdPerDSP. +func (mr *MockCacheMockRecorder) GetFSCThresholdPerDSP() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFSCThresholdPerDSP", reflect.TypeOf((*MockCache)(nil).GetFSCThresholdPerDSP)) +} + +// GetMappingsFromCacheV25 mocks base method. +func (m *MockCache) GetMappingsFromCacheV25(arg0 models.RequestCtx, arg1 int) map[string]models.SlotMapping { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMappingsFromCacheV25", arg0, arg1) + ret0, _ := ret[0].(map[string]models.SlotMapping) + return ret0 +} + +// GetMappingsFromCacheV25 indicates an expected call of GetMappingsFromCacheV25. +func (mr *MockCacheMockRecorder) GetMappingsFromCacheV25(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMappingsFromCacheV25", reflect.TypeOf((*MockCache)(nil).GetMappingsFromCacheV25), arg0, arg1) +} + +// GetPartnerConfigMap mocks base method. +func (m *MockCache) GetPartnerConfigMap(arg0, arg1, arg2 int, arg3 string) (map[int]map[string]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPartnerConfigMap", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(map[int]map[string]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPartnerConfigMap indicates an expected call of GetPartnerConfigMap. +func (mr *MockCacheMockRecorder) GetPartnerConfigMap(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPartnerConfigMap", reflect.TypeOf((*MockCache)(nil).GetPartnerConfigMap), arg0, arg1, arg2, arg3) +} + +// GetPublisherVASTTagsFromCache mocks base method. +func (m *MockCache) GetPublisherVASTTagsFromCache(arg0 int) map[int]*models.VASTTag { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPublisherVASTTagsFromCache", arg0) + ret0, _ := ret[0].(map[int]*models.VASTTag) + return ret0 +} + +// GetPublisherVASTTagsFromCache indicates an expected call of GetPublisherVASTTagsFromCache. +func (mr *MockCacheMockRecorder) GetPublisherVASTTagsFromCache(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublisherVASTTagsFromCache", reflect.TypeOf((*MockCache)(nil).GetPublisherVASTTagsFromCache), arg0) +} + +// GetSlotToHashValueMapFromCacheV25 mocks base method. +func (m *MockCache) GetSlotToHashValueMapFromCacheV25(arg0 models.RequestCtx, arg1 int) models.SlotMappingInfo { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSlotToHashValueMapFromCacheV25", arg0, arg1) + ret0, _ := ret[0].(models.SlotMappingInfo) + return ret0 +} + +// GetSlotToHashValueMapFromCacheV25 indicates an expected call of GetSlotToHashValueMapFromCacheV25. +func (mr *MockCacheMockRecorder) GetSlotToHashValueMapFromCacheV25(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSlotToHashValueMapFromCacheV25", reflect.TypeOf((*MockCache)(nil).GetSlotToHashValueMapFromCacheV25), arg0, arg1) +} + +// GetTBFTrafficForPublishers mocks base method. +func (m *MockCache) GetTBFTrafficForPublishers() (map[int]map[int]int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTBFTrafficForPublishers") + ret0, _ := ret[0].(map[int]map[int]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTBFTrafficForPublishers indicates an expected call of GetTBFTrafficForPublishers. +func (mr *MockCacheMockRecorder) GetTBFTrafficForPublishers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTBFTrafficForPublishers", reflect.TypeOf((*MockCache)(nil).GetTBFTrafficForPublishers)) +} + +// Set mocks base method. +func (m *MockCache) Set(arg0 string, arg1 interface{}) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Set", arg0, arg1) +} + +// Set indicates an expected call of Set. +func (mr *MockCacheMockRecorder) Set(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockCache)(nil).Set), arg0, arg1) +} diff --git a/modules/pubmatic/openwrap/config/config.go b/modules/pubmatic/openwrap/config/config.go new file mode 100755 index 00000000000..3eeb6fb7406 --- /dev/null +++ b/modules/pubmatic/openwrap/config/config.go @@ -0,0 +1,94 @@ +package config + +import ( + "time" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/stats" +) + +// Config contains the values read from the config file at boot time +type Config struct { + Server Server + Database Database + Cache Cache + Timeout Timeout + Tracker Tracker + PixelView PixelView + Features FeatureToggle + Log Log + Stats stats.Stats +} + +type Server struct { + HostName string + DCName string //Name of the data center +} + +type Database struct { + Host string + Port int + + Database string + User string + Pass string + + IdleConnection, MaxConnection, ConnMaxLifeTime, MaxDbContextTimeout int + + Queries Queries +} + +/* +GetParterConfig query to get all partners and related configurations for a given pub,profile,version + +Data is ordered by partnerId,keyname and entityId so that version level partner params will override the account level partner parasm in the code logic +*/ +type Queries struct { + GetParterConfig string + DisplayVersionInnerQuery string + LiveVersionInnerQuery string + GetWrapperSlotMappingsQuery string + GetWrapperLiveVersionSlotMappings string + GetPMSlotToMappings string + GetAdunitConfigQuery string + GetAdunitConfigForLiveVersion string + GetSlotNameHash string + GetPublisherVASTTagsQuery string + GetAllFscDisabledPublishersQuery string + GetAllDspFscPcntQuery string + GetTBFRateQuery string +} + +type Cache struct { + CacheConTimeout int // Connection timeout for cache + + CacheDefaultExpiry int // in seconds + VASTTagCacheExpiry int // in seconds +} + +type Timeout struct { + MaxTimeout int64 + MinTimeout int64 + HBTimeout int64 +} + +type Tracker struct { + Endpoint string + VideoErrorTrackerEndpoint string +} + +type PixelView struct { + OMScript string //js script path for conditional tracker call fire +} + +type FeatureToggle struct { +} + +type Log struct { //Log Details + LogPath string + LogLevel int + MaxLogSize uint64 + MaxLogFiles int + LogRotationTime time.Duration + DebugLogUpdateTime time.Duration + DebugAuthKey string +} diff --git a/modules/pubmatic/openwrap/contenttransperencyobject.go b/modules/pubmatic/openwrap/contenttransperencyobject.go new file mode 100644 index 00000000000..89624688638 --- /dev/null +++ b/modules/pubmatic/openwrap/contenttransperencyobject.go @@ -0,0 +1,59 @@ +package openwrap + +import ( + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// setContentObjectTransparencyObject from request or AdUnit Object +// setContentObjectTransparencyObject from request or AdUnit Object +func setContentTransparencyObject(rctx models.RequestCtx, reqExt *models.RequestExt) (prebidTransparency *openrtb_ext.TransparencyExt) { + if reqExt.Prebid.Transparency != nil { + return + } + + for _, impCtx := range rctx.ImpBidCtx { + var transparency *adunitconfig.Transparency + + if impCtx.BannerAdUnitCtx.AppliedSlotAdUnitConfig != nil && impCtx.BannerAdUnitCtx.AppliedSlotAdUnitConfig.Transparency != nil { + transparency = impCtx.BannerAdUnitCtx.AppliedSlotAdUnitConfig.Transparency + } else if impCtx.VideoAdUnitCtx.AppliedSlotAdUnitConfig != nil && impCtx.VideoAdUnitCtx.AppliedSlotAdUnitConfig.Transparency != nil { + transparency = impCtx.VideoAdUnitCtx.AppliedSlotAdUnitConfig.Transparency + } + + if transparency == nil || len(transparency.Content.Mappings) == 0 { + continue + } + + content := make(map[string]openrtb_ext.TransparencyRule) + + for _, partnerConfig := range rctx.PartnerConfigMap { + bidder := partnerConfig[models.BidderCode] + + _, ok := rctx.AdapterThrottleMap[bidder] + if ok || partnerConfig[models.SERVER_SIDE_FLAG] != "1" { + continue + } + + for _, rule := range getRules(rctx.Source, bidder) { + if transparencyRule, ok := transparency.Content.Mappings[rule]; ok { + content[bidder] = transparencyRule + break + } + } + } + + if len(content) > 0 { + return &openrtb_ext.TransparencyExt{ + Content: content, + } + } + } + + return nil +} + +func getRules(source, bidder string) []string { + return []string{source + "|" + bidder, "*|" + bidder, source + "|*", "*|*"} +} diff --git a/modules/pubmatic/openwrap/database/database.go b/modules/pubmatic/openwrap/database/database.go new file mode 100644 index 00000000000..97f60eed38b --- /dev/null +++ b/modules/pubmatic/openwrap/database/database.go @@ -0,0 +1,18 @@ +package database + +import ( + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +type Database interface { + GetAdunitConfig(profileID, displayVersion int) (*adunitconfig.AdUnitConfig, error) + GetActivePartnerConfigurations(pubID, profileID, displayVersion int) (map[int]map[string]string, error) + GetPublisherSlotNameHash(pubID int) (map[string]string, error) + GetWrapperSlotMappings(partnerConfigMap map[int]map[string]string, profileID, displayVersion int) (map[int][]models.SlotMapping, error) + GetPublisherVASTTags(pubID int) (models.PublisherVASTTags, error) + GetMappings(slotKey string, slotMap map[string]models.SlotMapping) (map[string]interface{}, error) + GetFSCDisabledPublishers() (map[int]struct{}, error) + GetFSCThresholdPerDSP() (map[int]int, error) + GetTBFTrafficForPublishers() (map[int]map[int]int, error) +} diff --git a/modules/pubmatic/openwrap/database/mock/mock.go b/modules/pubmatic/openwrap/database/mock/mock.go new file mode 100644 index 00000000000..65174c2164d --- /dev/null +++ b/modules/pubmatic/openwrap/database/mock/mock.go @@ -0,0 +1,171 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/PubMatic-OpenWrap/prebid-server/modules/pubmatic/openwrap/database (interfaces: Database) + +// Package mock_database is a generated GoMock package. +package mock_database + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + models "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + adunitconfig "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +// MockDatabase is a mock of Database interface. +type MockDatabase struct { + ctrl *gomock.Controller + recorder *MockDatabaseMockRecorder +} + +// MockDatabaseMockRecorder is the mock recorder for MockDatabase. +type MockDatabaseMockRecorder struct { + mock *MockDatabase +} + +// NewMockDatabase creates a new mock instance. +func NewMockDatabase(ctrl *gomock.Controller) *MockDatabase { + mock := &MockDatabase{ctrl: ctrl} + mock.recorder = &MockDatabaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDatabase) EXPECT() *MockDatabaseMockRecorder { + return m.recorder +} + +// GetActivePartnerConfigurations mocks base method. +func (m *MockDatabase) GetActivePartnerConfigurations(arg0, arg1, arg2 int) (map[int]map[string]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetActivePartnerConfigurations", arg0, arg1, arg2) + ret0, _ := ret[0].(map[int]map[string]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetActivePartnerConfigurations indicates an expected call of GetActivePartnerConfigurations. +func (mr *MockDatabaseMockRecorder) GetActivePartnerConfigurations(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActivePartnerConfigurations", reflect.TypeOf((*MockDatabase)(nil).GetActivePartnerConfigurations), arg0, arg1, arg2) +} + +// GetAdunitConfig mocks base method. +func (m *MockDatabase) GetAdunitConfig(arg0, arg1 int) (*adunitconfig.AdUnitConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAdunitConfig", arg0, arg1) + ret0, _ := ret[0].(*adunitconfig.AdUnitConfig) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAdunitConfig indicates an expected call of GetAdunitConfig. +func (mr *MockDatabaseMockRecorder) GetAdunitConfig(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAdunitConfig", reflect.TypeOf((*MockDatabase)(nil).GetAdunitConfig), arg0, arg1) +} + +// GetFSCDisabledPublishers mocks base method. +func (m *MockDatabase) GetFSCDisabledPublishers() (map[int]struct{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFSCDisabledPublishers") + ret0, _ := ret[0].(map[int]struct{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFSCDisabledPublishers indicates an expected call of GetFSCDisabledPublishers. +func (mr *MockDatabaseMockRecorder) GetFSCDisabledPublishers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFSCDisabledPublishers", reflect.TypeOf((*MockDatabase)(nil).GetFSCDisabledPublishers)) +} + +// GetFSCThresholdPerDSP mocks base method. +func (m *MockDatabase) GetFSCThresholdPerDSP() (map[int]int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFSCThresholdPerDSP") + ret0, _ := ret[0].(map[int]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFSCThresholdPerDSP indicates an expected call of GetFSCThresholdPerDSP. +func (mr *MockDatabaseMockRecorder) GetFSCThresholdPerDSP() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFSCThresholdPerDSP", reflect.TypeOf((*MockDatabase)(nil).GetFSCThresholdPerDSP)) +} + +// GetMappings mocks base method. +func (m *MockDatabase) GetMappings(arg0 string, arg1 map[string]models.SlotMapping) (map[string]interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMappings", arg0, arg1) + ret0, _ := ret[0].(map[string]interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMappings indicates an expected call of GetMappings. +func (mr *MockDatabaseMockRecorder) GetMappings(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMappings", reflect.TypeOf((*MockDatabase)(nil).GetMappings), arg0, arg1) +} + +// GetPublisherSlotNameHash mocks base method. +func (m *MockDatabase) GetPublisherSlotNameHash(arg0 int) (map[string]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPublisherSlotNameHash", arg0) + ret0, _ := ret[0].(map[string]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPublisherSlotNameHash indicates an expected call of GetPublisherSlotNameHash. +func (mr *MockDatabaseMockRecorder) GetPublisherSlotNameHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublisherSlotNameHash", reflect.TypeOf((*MockDatabase)(nil).GetPublisherSlotNameHash), arg0) +} + +// GetPublisherVASTTags mocks base method. +func (m *MockDatabase) GetPublisherVASTTags(arg0 int) (map[int]*models.VASTTag, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPublisherVASTTags", arg0) + ret0, _ := ret[0].(map[int]*models.VASTTag) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPublisherVASTTags indicates an expected call of GetPublisherVASTTags. +func (mr *MockDatabaseMockRecorder) GetPublisherVASTTags(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublisherVASTTags", reflect.TypeOf((*MockDatabase)(nil).GetPublisherVASTTags), arg0) +} + +// GetTBFTrafficForPublishers mocks base method. +func (m *MockDatabase) GetTBFTrafficForPublishers() (map[int]map[int]int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTBFTrafficForPublishers") + ret0, _ := ret[0].(map[int]map[int]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTBFTrafficForPublishers indicates an expected call of GetTBFTrafficForPublishers. +func (mr *MockDatabaseMockRecorder) GetTBFTrafficForPublishers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTBFTrafficForPublishers", reflect.TypeOf((*MockDatabase)(nil).GetTBFTrafficForPublishers)) +} + +// GetWrapperSlotMappings mocks base method. +func (m *MockDatabase) GetWrapperSlotMappings(arg0 map[int]map[string]string, arg1, arg2 int) (map[int][]models.SlotMapping, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetWrapperSlotMappings", arg0, arg1, arg2) + ret0, _ := ret[0].(map[int][]models.SlotMapping) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetWrapperSlotMappings indicates an expected call of GetWrapperSlotMappings. +func (mr *MockDatabaseMockRecorder) GetWrapperSlotMappings(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWrapperSlotMappings", reflect.TypeOf((*MockDatabase)(nil).GetWrapperSlotMappings), arg0, arg1, arg2) +} diff --git a/modules/pubmatic/openwrap/database/mock_driver/mock.go b/modules/pubmatic/openwrap/database/mock_driver/mock.go new file mode 100644 index 00000000000..c5c653194ad --- /dev/null +++ b/modules/pubmatic/openwrap/database/mock_driver/mock.go @@ -0,0 +1,208 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: database/sql/driver (interfaces: Driver,Connector,Conn,DriverContext) + +// Package mock_driver is a generated GoMock package. +package mock_driver + +import ( + context "context" + driver "database/sql/driver" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockDriver is a mock of Driver interface. +type MockDriver struct { + ctrl *gomock.Controller + recorder *MockDriverMockRecorder +} + +// MockDriverMockRecorder is the mock recorder for MockDriver. +type MockDriverMockRecorder struct { + mock *MockDriver +} + +// NewMockDriver creates a new mock instance. +func NewMockDriver(ctrl *gomock.Controller) *MockDriver { + mock := &MockDriver{ctrl: ctrl} + mock.recorder = &MockDriverMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDriver) EXPECT() *MockDriverMockRecorder { + return m.recorder +} + +// Open mocks base method. +func (m *MockDriver) Open(arg0 string) (driver.Conn, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Open", arg0) + ret0, _ := ret[0].(driver.Conn) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Open indicates an expected call of Open. +func (mr *MockDriverMockRecorder) Open(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Open", reflect.TypeOf((*MockDriver)(nil).Open), arg0) +} + +// MockConnector is a mock of Connector interface. +type MockConnector struct { + ctrl *gomock.Controller + recorder *MockConnectorMockRecorder +} + +// MockConnectorMockRecorder is the mock recorder for MockConnector. +type MockConnectorMockRecorder struct { + mock *MockConnector +} + +// NewMockConnector creates a new mock instance. +func NewMockConnector(ctrl *gomock.Controller) *MockConnector { + mock := &MockConnector{ctrl: ctrl} + mock.recorder = &MockConnectorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConnector) EXPECT() *MockConnectorMockRecorder { + return m.recorder +} + +// Connect mocks base method. +func (m *MockConnector) Connect(arg0 context.Context) (driver.Conn, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Connect", arg0) + ret0, _ := ret[0].(driver.Conn) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Connect indicates an expected call of Connect. +func (mr *MockConnectorMockRecorder) Connect(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connect", reflect.TypeOf((*MockConnector)(nil).Connect), arg0) +} + +// Driver mocks base method. +func (m *MockConnector) Driver() driver.Driver { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Driver") + ret0, _ := ret[0].(driver.Driver) + return ret0 +} + +// Driver indicates an expected call of Driver. +func (mr *MockConnectorMockRecorder) Driver() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Driver", reflect.TypeOf((*MockConnector)(nil).Driver)) +} + +// MockConn is a mock of Conn interface. +type MockConn struct { + ctrl *gomock.Controller + recorder *MockConnMockRecorder +} + +// MockConnMockRecorder is the mock recorder for MockConn. +type MockConnMockRecorder struct { + mock *MockConn +} + +// NewMockConn creates a new mock instance. +func NewMockConn(ctrl *gomock.Controller) *MockConn { + mock := &MockConn{ctrl: ctrl} + mock.recorder = &MockConnMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConn) EXPECT() *MockConnMockRecorder { + return m.recorder +} + +// Begin mocks base method. +func (m *MockConn) Begin() (driver.Tx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Begin") + ret0, _ := ret[0].(driver.Tx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Begin indicates an expected call of Begin. +func (mr *MockConnMockRecorder) Begin() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Begin", reflect.TypeOf((*MockConn)(nil).Begin)) +} + +// Close mocks base method. +func (m *MockConn) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockConnMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockConn)(nil).Close)) +} + +// Prepare mocks base method. +func (m *MockConn) Prepare(arg0 string) (driver.Stmt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Prepare", arg0) + ret0, _ := ret[0].(driver.Stmt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Prepare indicates an expected call of Prepare. +func (mr *MockConnMockRecorder) Prepare(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Prepare", reflect.TypeOf((*MockConn)(nil).Prepare), arg0) +} + +// MockDriverContext is a mock of DriverContext interface. +type MockDriverContext struct { + ctrl *gomock.Controller + recorder *MockDriverContextMockRecorder +} + +// MockDriverContextMockRecorder is the mock recorder for MockDriverContext. +type MockDriverContextMockRecorder struct { + mock *MockDriverContext +} + +// NewMockDriverContext creates a new mock instance. +func NewMockDriverContext(ctrl *gomock.Controller) *MockDriverContext { + mock := &MockDriverContext{ctrl: ctrl} + mock.recorder = &MockDriverContextMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDriverContext) EXPECT() *MockDriverContextMockRecorder { + return m.recorder +} + +// OpenConnector mocks base method. +func (m *MockDriverContext) OpenConnector(arg0 string) (driver.Connector, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OpenConnector", arg0) + ret0, _ := ret[0].(driver.Connector) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// OpenConnector indicates an expected call of OpenConnector. +func (mr *MockDriverContextMockRecorder) OpenConnector(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenConnector", reflect.TypeOf((*MockDriverContext)(nil).OpenConnector), arg0) +} diff --git a/modules/pubmatic/openwrap/database/mysql/adunit_config.go b/modules/pubmatic/openwrap/database/mysql/adunit_config.go new file mode 100644 index 00000000000..98fbf8dd0a2 --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/adunit_config.go @@ -0,0 +1,58 @@ +package mysql + +import ( + "database/sql" + "encoding/json" + "strconv" + "strings" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +// GetAdunitConfig - Method to get adunit config for a given profile and display version from giym DB +func (db *mySqlDB) GetAdunitConfig(profileID, displayVersion int) (*adunitconfig.AdUnitConfig, error) { + adunitConfigQuery := db.cfg.Queries.GetAdunitConfigQuery + if displayVersion == 0 { + adunitConfigQuery = db.cfg.Queries.GetAdunitConfigForLiveVersion + } + adunitConfigQuery = strings.Replace(adunitConfigQuery, profileIdKey, strconv.Itoa(profileID), -1) + adunitConfigQuery = strings.Replace(adunitConfigQuery, displayVersionKey, strconv.Itoa(displayVersion), -1) + + var adunitConfigJSON string + err := db.conn.QueryRow(adunitConfigQuery).Scan(&adunitConfigJSON) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + + adunitConfig := &adunitconfig.AdUnitConfig{} + err = json.Unmarshal([]byte(adunitConfigJSON), &adunitConfig) + if err != nil { + return nil, adunitconfig.ErrAdUnitUnmarshal + } + + for k, v := range adunitConfig.Config { + adunitConfig.Config[strings.ToLower(k)] = v + // shall we delete the orignal key-val? + } + + if adunitConfig.ConfigPattern == "" { + //Default configPattern value is "_AU_" if not present in db config + adunitConfig.ConfigPattern = models.MACRO_AD_UNIT_ID + } + + // safe check for old legacy profiles + // new profiles cannot be created as UI-API has config object validation + if adunitConfig.Config == nil { + adunitConfig.Config = make(map[string]*adunitconfig.AdConfig) + } + + if _, ok := adunitConfig.Config["default"]; !ok { + adunitConfig.Config["default"] = &adunitconfig.AdConfig{} + } + + return adunitConfig, err +} diff --git a/modules/pubmatic/openwrap/database/mysql/adunit_config_test.go b/modules/pubmatic/openwrap/database/mysql/adunit_config_test.go new file mode 100644 index 00000000000..75968ed56b0 --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/adunit_config_test.go @@ -0,0 +1,261 @@ +package mysql + +import ( + "database/sql" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func Test_mySqlDB_GetAdunitConfig(t *testing.T) { + type fields struct { + cfg config.Database + } + type args struct { + profileID int + displayVersion int + } + tests := []struct { + name string + fields fields + args args + want *adunitconfig.AdUnitConfig + wantErr bool + setup func() *sql.DB + }{ + { + name: "empty query in config file", + want: nil, + wantErr: true, + setup: func() *sql.DB { + db, _, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + return db + }, + }, + { + name: "adunitconfig not found for profile(No rows error)", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetAdunitConfigForLiveVersion: "^SELECT (.+) FROM wrapper_media_config (.+) LIVE", + }, + }, + }, + args: args{ + profileID: 5890, + displayVersion: 0, + }, + want: nil, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{}) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_media_config (.+) LIVE")).WillReturnRows(rows) + return db + }, + }, + { + name: "query with display version id 0", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetAdunitConfigForLiveVersion: "^SELECT (.+) FROM wrapper_media_config (.+) LIVE", + }, + }, + }, + args: args{ + profileID: 5890, + displayVersion: 0, + }, + want: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Config: map[string]*adunitconfig.AdConfig{ + "default": {BidFloor: ptrutil.ToPtr(2.0)}, + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"adunitConfig"}).AddRow(`{"config":{"default":{"bidfloor":2}}}`) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_media_config (.+) LIVE")).WillReturnRows(rows) + return db + }, + }, + { + name: "query with non-zero display version id", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetAdunitConfigQuery: "^SELECT (.+) FROM wrapper_media_config (.+)", + }, + }, + }, + args: args{ + profileID: 5890, + displayVersion: 1, + }, + want: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Config: map[string]*adunitconfig.AdConfig{ + "default": {BidFloor: ptrutil.ToPtr(3.1)}, + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"adunitConfig"}).AddRow(`{"config":{"default":{"bidfloor":3.1}}}`) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_media_config (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "invalid adunitconfig json", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetAdunitConfigForLiveVersion: "^SELECT (.+) FROM wrapper_media_config (.+) LIVE", + }, + }, + }, + args: args{ + profileID: 5890, + displayVersion: 0, + }, + want: nil, + wantErr: true, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"adunitConfig"}).AddRow(`{`) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_media_config (.+) LIVE")).WillReturnRows(rows) + return db + }, + }, + { + name: "non-default config pattern in adunitconfig", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetAdunitConfigQuery: "^SELECT (.+) FROM wrapper_media_config (.+)", + }, + }, + }, + args: args{ + profileID: 5890, + displayVersion: 1, + }, + want: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_DIV_", + Config: map[string]*adunitconfig.AdConfig{ + "default": {BidFloor: ptrutil.ToPtr(3.1)}, + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"adunitConfig"}).AddRow(`{"configPattern": "_DIV_", "config":{"default":{"bidfloor":3.1}}}`) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_media_config (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "default adunit not present", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetAdunitConfigQuery: "^SELECT (.+) FROM wrapper_media_config (.+)", + }, + }, + }, + args: args{ + profileID: 5890, + displayVersion: 1, + }, + want: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_DIV_", + Config: map[string]*adunitconfig.AdConfig{ + "default": {}, + "abc": {BidFloor: ptrutil.ToPtr(3.1)}, + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"adunitConfig"}).AddRow(`{"configPattern": "_DIV_", "config":{"abc":{"bidfloor":3.1}}}`) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_media_config (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "config key not present", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetAdunitConfigQuery: "^SELECT (.+) FROM wrapper_media_config (.+)", + }, + }, + }, + args: args{ + profileID: 5890, + displayVersion: 1, + }, + want: &adunitconfig.AdUnitConfig{ + ConfigPattern: "_DIV_", + Config: map[string]*adunitconfig.AdConfig{ + "default": {}, + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"adunitConfig"}).AddRow(`{"configPattern": "_DIV_"}`) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_media_config (.+)")).WillReturnRows(rows) + return db + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &mySqlDB{ + conn: tt.setup(), + cfg: tt.fields.cfg, + } + defer db.conn.Close() + + got, err := db.GetAdunitConfig(tt.args.profileID, tt.args.displayVersion) + if (err != nil) != tt.wantErr { + t.Errorf("mySqlDB.GetAdunitConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/database/mysql/fullscreenclickability.go b/modules/pubmatic/openwrap/database/mysql/fullscreenclickability.go new file mode 100644 index 00000000000..45b4f34a4ae --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/fullscreenclickability.go @@ -0,0 +1,50 @@ +package mysql + +import ( + "strconv" + + "github.com/golang/glog" +) + +func (db *mySqlDB) GetFSCDisabledPublishers() (map[int]struct{}, error) { + rows, err := db.conn.Query(db.cfg.Queries.GetAllFscDisabledPublishersQuery) + if err != nil { + return nil, err + } + defer rows.Close() + + fscDisabledPublishers := make(map[int]struct{}) + for rows.Next() { + var pubid int + if err := rows.Scan(&pubid); err == nil { + fscDisabledPublishers[pubid] = struct{}{} + } + } + return fscDisabledPublishers, nil +} + +func (db *mySqlDB) GetFSCThresholdPerDSP() (map[int]int, error) { + rows, err := db.conn.Query(db.cfg.Queries.GetAllDspFscPcntQuery) + if err != nil { + return nil, err + } + defer rows.Close() + + fscDspThresholds := make(map[int]int) + for rows.Next() { + var dspId int + var fscThreshold string + if err := rows.Scan(&dspId, &fscThreshold); err != nil { + glog.Error("Error in getting dsp-thresholds details from DB:", err.Error()) + continue + } + // convert threshold string to int + pcnt, err := strconv.Atoi(fscThreshold) + if err != nil { + glog.Errorf("Invalid fsc_pcnt value for dspId:%d, threshold:%v", dspId, fscThreshold) + continue + } + fscDspThresholds[dspId] = pcnt + } + return fscDspThresholds, nil +} diff --git a/modules/pubmatic/openwrap/database/mysql/fullscreenclickability_test.go b/modules/pubmatic/openwrap/database/mysql/fullscreenclickability_test.go new file mode 100644 index 00000000000..5c7b6af111c --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/fullscreenclickability_test.go @@ -0,0 +1,188 @@ +package mysql + +import ( + "database/sql" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/stretchr/testify/assert" +) + +func Test_mySqlDB_GetFSCDisabledPublishers(t *testing.T) { + type fields struct { + cfg config.Database + } + tests := []struct { + name string + fields fields + want map[int]struct{} + wantErr bool + setup func() *sql.DB + }{ + { + name: "empty query in config file", + want: nil, + wantErr: true, + setup: func() *sql.DB { + db, _, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + return db + }, + }, + { + name: "invalid pubid", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetAllFscDisabledPublishersQuery: "^SELECT (.+) FROM wrapper_publisher_feature_mapping (.+)", + }, + }, + }, + want: map[int]struct{}{}, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"pub_id"}).AddRow(`5890,5891,5892`) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_feature_mapping (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "Valid rows returned, setting invalid values to 1", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetAllFscDisabledPublishersQuery: "^SELECT (.+) FROM wrapper_publisher_feature_mapping (.+)", + }, + }, + }, + want: map[int]struct{}{ + 5890: {}, + 5891: {}, + 5892: {}, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"pub_id"}). + AddRow(`5890`). + AddRow(`5891`). + AddRow(`5892`) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_feature_mapping (.+)")).WillReturnRows(rows) + return db + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &mySqlDB{ + conn: tt.setup(), + cfg: tt.fields.cfg, + } + got, err := db.GetFSCDisabledPublishers() + if (err != nil) != tt.wantErr { + t.Errorf("mySqlDB.GetFSCDisabledPublishers() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_mySqlDB_GetFSCThresholdPerDSP(t *testing.T) { + type fields struct { + cfg config.Database + } + tests := []struct { + name string + fields fields + want map[int]int + wantErr bool + setup func() *sql.DB + }{ + { + name: "empty query in config file", + want: nil, + wantErr: true, + setup: func() *sql.DB { + db, _, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + return db + }, + }, + { + name: "Invalid dsp_id", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetAllDspFscPcntQuery: "^SELECT (.+) FROM wrapper_feature_dsp_mapping (.+)", + }, + }, + }, + want: map[int]int{}, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"dsp_id", "value"}).AddRow(`5,23`, `24`) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_feature_dsp_mapping (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "Valid rows returned,avoiding floating pcnt values", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetAllDspFscPcntQuery: "^SELECT (.+) FROM wrapper_feature_dsp_mapping (.+)", + }, + }, + }, + want: map[int]int{ + 5: 24, + 8: 20, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"dsp_id", "value"}). + AddRow(`5`, `24`). + AddRow(`8`, `20`). + AddRow(`9`, `12.12`) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_feature_dsp_mapping (.+)")).WillReturnRows(rows) + return db + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &mySqlDB{ + conn: tt.setup(), + cfg: tt.fields.cfg, + } + got, err := db.GetFSCThresholdPerDSP() + if (err != nil) != tt.wantErr { + t.Errorf("mySqlDB.GetFSCThresholdPerDSP() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/database/mysql/mysql.go b/modules/pubmatic/openwrap/database/mysql/mysql.go new file mode 100644 index 00000000000..4b3d8edf1ed --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/mysql.go @@ -0,0 +1,24 @@ +package mysql + +import ( + "database/sql" + "sync" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" +) + +type mySqlDB struct { + conn *sql.DB + cfg config.Database +} + +var db *mySqlDB +var dbOnce sync.Once + +func New(conn *sql.DB, cfg config.Database) *mySqlDB { + dbOnce.Do( + func() { + db = &mySqlDB{conn: conn, cfg: cfg} + }) + return db +} diff --git a/modules/pubmatic/openwrap/database/mysql/mysql_test.go b/modules/pubmatic/openwrap/database/mysql/mysql_test.go new file mode 100644 index 00000000000..c6d363bd4c8 --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/mysql_test.go @@ -0,0 +1,40 @@ +package mysql + +import ( + "database/sql" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/stretchr/testify/assert" +) + +func TestNew(t *testing.T) { + type args struct { + conn *sql.DB + cfg config.Database + } + tests := []struct { + name string + args args + setup func() *sql.DB + }{ + { + name: "test", + args: args{ + cfg: config.Database{}, + }, + setup: func() *sql.DB { + db, _, _ := sqlmock.New() + return db + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.args.conn = tt.setup() + db := New(tt.args.conn, tt.args.cfg) + assert.NotNil(t, db, "db should not be nil") + }) + } +} diff --git a/modules/pubmatic/openwrap/database/mysql/partner_config.go b/modules/pubmatic/openwrap/database/mysql/partner_config.go new file mode 100644 index 00000000000..4089900a7db --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/partner_config.go @@ -0,0 +1,92 @@ +package mysql + +import ( + "context" + "database/sql" + "fmt" + "strconv" + "time" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +// return the list of active server side header bidding partners +// with their configurations at publisher-profile-version level +func (db *mySqlDB) GetActivePartnerConfigurations(pubID, profileID int, displayVersion int) (map[int]map[string]string, error) { + versionID, displayVersionID, err := db.getVersionID(profileID, displayVersion, pubID) + if err != nil { + return nil, err + } + + partnerConfigMap, err := db.getActivePartnerConfigurations(versionID) + if err == nil && partnerConfigMap[-1] != nil { + partnerConfigMap[-1][models.DisplayVersionID] = strconv.Itoa(displayVersionID) + } + return partnerConfigMap, err +} + +func (db *mySqlDB) getActivePartnerConfigurations(versionID int) (map[int]map[string]string, error) { + getActivePartnersQuery := fmt.Sprintf(db.cfg.Queries.GetParterConfig, db.cfg.MaxDbContextTimeout, versionID, versionID, versionID) + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*time.Duration(db.cfg.MaxDbContextTimeout))) + defer cancel() + rows, err := db.conn.QueryContext(ctx, getActivePartnersQuery) + if err != nil { + return nil, err + } + defer rows.Close() + + partnerConfigMap := make(map[int]map[string]string, 0) + for rows.Next() { + var ( + keyName, value, prebidPartnerName, bidderCode string + partnerID, entityTypeID, testConfig, isAlias int + ) + if err := rows.Scan(&partnerID, &prebidPartnerName, &bidderCode, &isAlias, &entityTypeID, &testConfig, &keyName, &value); err != nil { + continue + } + + _, ok := partnerConfigMap[partnerID] + //below logic will take care of overriding account level partner keys with version level partner keys + //if key name is same for a given partnerID (Ref ticket: UOE-5647) + if !ok { + partnerConfigMap[partnerID] = map[string]string{models.PARTNER_ID: strconv.Itoa(partnerID)} + } + + if testConfig == 1 { + keyName = keyName + "_test" + partnerConfigMap[partnerID][models.PartnerTestEnabledKey] = "1" + } + + partnerConfigMap[partnerID][keyName] = value + + if _, ok := partnerConfigMap[partnerID][models.PREBID_PARTNER_NAME]; !ok && prebidPartnerName != "-" { + partnerConfigMap[partnerID][models.PREBID_PARTNER_NAME] = prebidPartnerName + partnerConfigMap[partnerID][models.BidderCode] = bidderCode + partnerConfigMap[partnerID][models.IsAlias] = strconv.Itoa(isAlias) + } + } + + // NYC_TODO: ignore close error + if err = rows.Err(); err != nil { + glog.Errorf("partner config row scan failed for versionID %d", versionID) + } + return partnerConfigMap, nil +} + +func (db *mySqlDB) getVersionID(profileID, displayVersion, pubID int) (int, int, error) { + var row *sql.Row + if displayVersion == 0 { + row = db.conn.QueryRow(db.cfg.Queries.LiveVersionInnerQuery, profileID, pubID) + } else { + row = db.conn.QueryRow(db.cfg.Queries.DisplayVersionInnerQuery, profileID, displayVersion, pubID) + } + + var versionID, displayVersionIDFromDB int + err := row.Scan(&versionID, &displayVersionIDFromDB) + if err != nil { + return versionID, displayVersionIDFromDB, err + } + return versionID, displayVersionIDFromDB, nil +} diff --git a/modules/pubmatic/openwrap/database/mysql/partner_config_test.go b/modules/pubmatic/openwrap/database/mysql/partner_config_test.go new file mode 100644 index 00000000000..061880ff2d8 --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/partner_config_test.go @@ -0,0 +1,665 @@ +package mysql + +import ( + "database/sql" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/stretchr/testify/assert" +) + +func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { + type fields struct { + cfg config.Database + } + type args struct { + pubID int + profileID int + displayVersion int + } + tests := []struct { + name string + fields fields + args args + want map[int]map[string]string + wantErr bool + setup func() *sql.DB + }{ + { + name: "invalid verison id", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + LiveVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+) LIVE", + }, + }, + }, + args: args{ + pubID: 5890, + profileID: 19109, + displayVersion: 0, + }, + + want: nil, + wantErr: true, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("25_1", "9") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+) LIVE")).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) + + return db + }, + }, + { + name: "error getting partnercofnig", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + LiveVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+) LIVE", + }, + }, + }, + args: args{ + pubID: 5890, + profileID: 19109, + displayVersion: 0, + }, + + want: nil, + wantErr: true, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("251", "9") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+) LIVE")).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) + + return db + }, + }, + { + name: "valid partnerconfig with displayversion is 0", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + LiveVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+) LIVE", + GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + }, + MaxDbContextTimeout: 1000, + }, + }, + args: args{ + pubID: 5890, + profileID: 19109, + displayVersion: 0, + }, + + want: map[int]map[string]string{ + 101: { + "bidderCode": "pubmatic", + "prebidPartnerName": "pubmatic", + "timeout": "200", + "kgp": "_AU_@_W_x_H_", + "serverSideEnabled": "1", + "isAlias": "0", + "partnerId": "101", + }, + -1: { + "bidderCode": "ALL", + "prebidPartnerName": "ALL", + "gdpr": "0", + "isAlias": "0", + "partnerId": "-1", + "displayVersionId": "9", + "platform": "display", + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("251", "9") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+) LIVE")).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) + + rowsPartnerConfig := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "keyName", "value"}). + AddRow("-1", "ALL", "ALL", 0, -1, 0, "platform", "display"). + AddRow("-1", "ALL", "ALL", 0, -1, 0, "gdpr", "0"). + AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, "kgp", "_AU_@_W_x_H_"). + AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, "timeout", "200"). + AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, "serverSideEnabled", "1") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rowsPartnerConfig) + return db + }, + }, + { + name: "valid partnerconfig with displayversion is not 0", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + DisplayVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+)", + GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + }, + MaxDbContextTimeout: 1000, + }, + }, + args: args{ + pubID: 5890, + profileID: 19109, + displayVersion: 3, + }, + + want: map[int]map[string]string{ + 101: { + "bidderCode": "pubmatic", + "prebidPartnerName": "pubmatic", + "timeout": "200", + "kgp": "_AU_@_W_x_H_", + "serverSideEnabled": "1", + "isAlias": "0", + "partnerId": "101", + }, + -1: { + "bidderCode": "ALL", + "prebidPartnerName": "ALL", + "gdpr": "0", + "isAlias": "0", + "partnerId": "-1", + "displayVersionId": "9", + "platform": "display", + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("251", "9") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+)")).WithArgs(19109, 3, 5890).WillReturnRows(rowsWrapperVersion) + + rowsPartnerConfig := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "keyName", "value"}). + AddRow("-1", "ALL", "ALL", 0, -1, 0, "platform", "display"). + AddRow("-1", "ALL", "ALL", 0, -1, 0, "gdpr", "0"). + AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, "kgp", "_AU_@_W_x_H_"). + AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, "timeout", "200"). + AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, "serverSideEnabled", "1") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rowsPartnerConfig) + return db + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &mySqlDB{ + conn: tt.setup(), + cfg: tt.fields.cfg, + } + got, err := db.GetActivePartnerConfigurations(tt.args.pubID, tt.args.profileID, tt.args.displayVersion) + if (err != nil) != tt.wantErr { + t.Errorf("mySqlDB.GetActivePartnerConfigurations() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { + type fields struct { + cfg config.Database + } + type args struct { + versionID int + } + tests := []struct { + name string + fields fields + args args + want map[int]map[string]string + wantErr bool + setup func() *sql.DB + }{ + { + name: "empty query in config file", + want: nil, + wantErr: true, + setup: func() *sql.DB { + db, _, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + return db + }, + }, + { + name: "incorrect datatype of partner_id ", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + }, + MaxDbContextTimeout: 1000, + }, + }, + args: args{ + versionID: 1, + }, + want: map[int]map[string]string{}, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "keyName", "value"}). + AddRow("11_11", "openx", "openx", 0, -1, 0, "k1", "v1") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "default display version", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + }, + MaxDbContextTimeout: 1000, + }, + }, + args: args{ + versionID: 123, + }, + want: map[int]map[string]string{ + 101: { + "k1": "v1", + "k2": "v2", + "partnerId": "101", + "prebidPartnerName": "openx", + "bidderCode": "openx", + "isAlias": "0", + }, + 102: { + "k1": "v2", + "partnerId": "102", + "prebidPartnerName": "pubmatic", + "bidderCode": "pubmatic", + "isAlias": "0", + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "keyName", "value"}). + AddRow(101, "openx", "openx", 0, -1, 0, "k1", "v1"). + AddRow(101, "openx", "openx", 0, -1, 0, "k2", "v2"). + AddRow(102, "pubmatic", "pubmatic", 0, -1, 0, "k1", "v2") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "account params present", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + }, + MaxDbContextTimeout: 1000, + }, + }, + args: args{ + versionID: 123, + }, + want: map[int]map[string]string{ + 101: { + "accountId": "9876", + "pubId": "8888", + "rev_share": "10", + "partnerId": "101", + "prebidPartnerName": "FirstPartnerName", + "bidderCode": "FirstBidder", + "isAlias": "0", + }, + 102: { + "k1": "v1", + "k2": "v2", + "partnerId": "102", + "prebidPartnerName": "SecondPartnerName", + "bidderCode": "SecondBidder", + "isAlias": "0", + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "keyName", "value"}). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 1, 0, "accountId", "1234"). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 3, 0, "accountId", "9876"). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 1, 0, "pubId", "9999"). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 3, 0, "pubId", "8888"). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 3, 0, "rev_share", "10"). + AddRow(102, "SecondPartnerName", "SecondBidder", 0, -1, 0, "k1", "v1"). + AddRow(102, "SecondPartnerName", "SecondBidder", 0, -1, 0, "k2", "v2") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "AB Test Enabled", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + }, + MaxDbContextTimeout: 1000, + }, + }, + args: args{ + versionID: 123, + }, + want: map[int]map[string]string{ + 101: { + "accountId": "1234", + "pubId": "8888", + "rev_share": "10", + "partnerId": "101", + "prebidPartnerName": "FirstPartnerName", + "bidderCode": "FirstBidder", + "sstimeout": "200", + "sstimeout_test": "350", + "testEnabled": "1", + "isAlias": "0", + }, + 102: { + "k1": "v1", + "k2": "v2", + "partnerId": "102", + "prebidPartnerName": "SecondPartnerName", + "bidderCode": "SecondBidder", + "isAlias": "0", + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "keyName", "value"}). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 1, 0, "accountId", "1234"). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 1, 0, "sstimeout", "200"). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 1, 1, "sstimeout", "350"). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 3, 0, "pubId", "8888"). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 3, 0, "rev_share", "10"). + AddRow(102, "SecondPartnerName", "SecondBidder", 0, -1, 0, "k1", "v1"). + AddRow(102, "SecondPartnerName", "SecondBidder", 0, -1, 0, "k2", "v2") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "bidder alias present", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + }, + MaxDbContextTimeout: 1000, + }, + }, + args: args{ + versionID: 123, + }, + + want: map[int]map[string]string{ + 101: { + "accountId": "1234", + "pubId": "8888", + "rev_share": "10", + "partnerId": "101", + "prebidPartnerName": "FirstPartnerName", + "bidderCode": "FirstBidder", + "sstimeout": "200", + "isAlias": "0", + }, + 102: { + "k1": "v1", + "k2": "v2", + "partnerId": "102", + "prebidPartnerName": "SecondPartnerName", + "bidderCode": "SecondBidder", + "isAlias": "1", + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "keyName", "value"}). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 1, 0, "accountId", "1234"). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 1, 0, "sstimeout", "200"). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 3, 0, "pubId", "8888"). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 3, 0, "rev_share", "10"). + AddRow(102, "SecondPartnerName", "SecondBidder", 1, -1, 0, "k1", "v1"). + AddRow(102, "SecondPartnerName", "SecondBidder", 1, -1, 0, "k2", "v2") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "partnerName as `-`", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + }, + MaxDbContextTimeout: 1000, + }, + }, + args: args{ + versionID: 123, + }, + want: map[int]map[string]string{ + 101: { + "accountId": "1234", + "pubId": "12345", + "rev_share": "10", + "partnerId": "101", + "prebidPartnerName": "FirstPartnerName", + "bidderCode": "FirstBidder", + "sstimeout": "200", + "isAlias": "0", + }, + 102: { + "k1": "v1", + "k2": "v2", + "partnerId": "102", + "prebidPartnerName": "SecondPartnerName", + "bidderCode": "SecondBidder", + "isAlias": "0", + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "keyName", "value"}). + AddRow(101, "-", "-", 0, 1, 0, "accountId", "1234"). + AddRow(101, "-", "-", 0, 1, 0, "sstimeout", "200"). + AddRow(101, "-", "-", 0, 1, 0, "pubId", "8888"). + AddRow(101, "-", "-", 0, 3, 0, "pubId", "12345"). + AddRow(101, "FirstPartnerName", "FirstBidder", 0, 3, 0, "rev_share", "10"). + AddRow(102, "-", "-", 0, -1, 0, "k1", "v1"). + AddRow(102, "SecondPartnerName", "SecondBidder", 0, -1, 0, "k2", "v2") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rows) + return db + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &mySqlDB{ + conn: tt.setup(), + cfg: tt.fields.cfg, + } + got, err := db.getActivePartnerConfigurations(tt.args.versionID) + if (err != nil) != tt.wantErr { + t.Errorf("mySqlDB.getActivePartnerConfigurations() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_mySqlDB_getVersionID(t *testing.T) { + type fields struct { + cfg config.Database + } + type args struct { + profileID int + displayVersion int + pubID int + } + tests := []struct { + name string + fields fields + args args + expectedVersionID int + expectedDisplayVersionIDFromDB int + wantErr bool + setup func() *sql.DB + }{ + { + name: "invalid verison id", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + LiveVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+) LIVE", + }, + }, + }, + args: args{ + profileID: 19109, + displayVersion: 0, + pubID: 5890, + }, + expectedVersionID: 0, + expectedDisplayVersionIDFromDB: 0, + wantErr: true, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("25_1", "9") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+) LIVE")).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) + + return db + }, + }, + { + name: "displayversion is 0", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + LiveVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+) LIVE", + }, + }, + }, + args: args{ + profileID: 19109, + displayVersion: 0, + pubID: 5890, + }, + + expectedVersionID: 251, + expectedDisplayVersionIDFromDB: 9, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("251", "9") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+) LIVE")).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) + + return db + }, + }, + { + name: "displayversion is not 0", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + DisplayVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+)", + }, + }, + }, + args: args{ + profileID: 19109, + displayVersion: 3, + pubID: 5890, + }, + + expectedVersionID: 251, + expectedDisplayVersionIDFromDB: 9, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("251", "9") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+)")).WithArgs(19109, 3, 5890).WillReturnRows(rowsWrapperVersion) + + return db + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &mySqlDB{ + conn: tt.setup(), + cfg: tt.fields.cfg, + } + got, got1, err := db.getVersionID(tt.args.profileID, tt.args.displayVersion, tt.args.pubID) + if (err != nil) != tt.wantErr { + t.Errorf("mySqlDB.getVersionID() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.expectedVersionID { + t.Errorf("mySqlDB.getVersionID() got = %v, want %v", got, tt.expectedVersionID) + } + if got1 != tt.expectedDisplayVersionIDFromDB { + t.Errorf("mySqlDB.getVersionID() got1 = %v, want %v", got1, tt.expectedDisplayVersionIDFromDB) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/database/mysql/queries.go b/modules/pubmatic/openwrap/database/mysql/queries.go new file mode 100644 index 00000000000..0f59dd745a3 --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/queries.go @@ -0,0 +1,9 @@ +package mysql + +const ( + partnerIdKey = "#PARTNER_IDS" + profileIdKey = "#PROFILE_ID" + pubIdKey = "#PUB_ID" + displayVersionKey = "#DISPLAY_VERSION" + versionIdKey = "#VERSION_ID" +) diff --git a/modules/pubmatic/openwrap/database/mysql/slot_mapping.go b/modules/pubmatic/openwrap/database/mysql/slot_mapping.go new file mode 100644 index 00000000000..11135599a1f --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/slot_mapping.go @@ -0,0 +1,99 @@ +package mysql + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +// GetPublisherSlotNameHash Returns a map of all slot names and hashes for a publisher +func (db *mySqlDB) GetPublisherSlotNameHash(pubID int) (map[string]string, error) { + nameHashMap := make(map[string]string) + + query := db.formSlotNameHashQuery(pubID) + rows, err := db.conn.Query(query) + if err != nil { + return nameHashMap, err + } + defer rows.Close() + + for rows.Next() { + var name, hash string + if err = rows.Scan(&name, &hash); err != nil { + continue + } + nameHashMap[name] = hash + } + + //vastTagHookPublisherSlotName(nameHashMap, pubID) + return nameHashMap, nil +} + +// Return the list of wrapper slot mappings +func (db *mySqlDB) GetWrapperSlotMappings(partnerConfigMap map[int]map[string]string, profileID, displayVersion int) (map[int][]models.SlotMapping, error) { + partnerSlotMappingMap := make(map[int][]models.SlotMapping) + + query := db.formWrapperSlotMappingQuery(profileID, displayVersion, partnerConfigMap) + rows, err := db.conn.Query(query) + if err != nil { + return partnerSlotMappingMap, err + } + defer rows.Close() + + for rows.Next() { + var slotMapping = models.SlotMapping{} + err := rows.Scan(&slotMapping.PartnerId, &slotMapping.AdapterId, &slotMapping.VersionId, &slotMapping.SlotName, &slotMapping.MappingJson, &slotMapping.OrderID) + if err != nil { + continue + } + + slotMappingList, found := partnerSlotMappingMap[int(slotMapping.PartnerId)] + if found { + slotMappingList = append(slotMappingList, slotMapping) + partnerSlotMappingMap[int(slotMapping.PartnerId)] = slotMappingList + } else { + newSlotMappingList := make([]models.SlotMapping, 0) + newSlotMappingList = append(newSlotMappingList, slotMapping) + partnerSlotMappingMap[int(slotMapping.PartnerId)] = newSlotMappingList + } + + } + //vastTagHookPartnerSlotMapping(partnerSlotMappingMap, profileId, displayVersion) + return partnerSlotMappingMap, nil +} + +// GetMappings will returns slotMapping from map based on slotKey +func (db *mySqlDB) GetMappings(slotKey string, slotMap map[string]models.SlotMapping) (map[string]interface{}, error) { + slotMappingObj, present := slotMap[strings.ToLower(slotKey)] + if !present { + return nil, errors.New("No mapping found for slot:" + slotKey) + } + fieldMap := slotMappingObj.SlotMappings + return fieldMap, nil +} + +func (db *mySqlDB) formWrapperSlotMappingQuery(profileID, displayVersion int, partnerConfigMap map[int]map[string]string) string { + var query string + var partnerIDStr string + for partnerID := range partnerConfigMap { + partnerIDStr = partnerIDStr + strconv.Itoa(partnerID) + "," + } + partnerIDStr = strings.TrimSuffix(partnerIDStr, ",") + + if displayVersion != 0 { + query = strings.Replace(db.cfg.Queries.GetWrapperSlotMappingsQuery, profileIdKey, strconv.Itoa(profileID), -1) + query = strings.Replace(query, displayVersionKey, strconv.Itoa(displayVersion), -1) + query = strings.Replace(query, partnerIdKey, partnerIDStr, -1) + } else { + query = strings.Replace(db.cfg.Queries.GetWrapperLiveVersionSlotMappings, profileIdKey, strconv.Itoa(profileID), -1) + query = strings.Replace(query, partnerIdKey, partnerIDStr, -1) + } + return query +} + +func (db *mySqlDB) formSlotNameHashQuery(pubID int) (query string) { + return fmt.Sprintf(db.cfg.Queries.GetSlotNameHash, pubID) +} diff --git a/modules/pubmatic/openwrap/database/mysql/slot_mapping_test.go b/modules/pubmatic/openwrap/database/mysql/slot_mapping_test.go new file mode 100644 index 00000000000..313714d7c9d --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/slot_mapping_test.go @@ -0,0 +1,417 @@ +package mysql + +import ( + "database/sql" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/stretchr/testify/assert" +) + +func Test_mySqlDB_GetPublisherSlotNameHash(t *testing.T) { + type fields struct { + cfg config.Database + } + type args struct { + pubID int + } + tests := []struct { + name string + fields fields + args args + want map[string]string + wantErr bool + setup func() *sql.DB + }{ + { + name: "empty query in config file", + want: map[string]string{}, + wantErr: true, + setup: func() *sql.DB { + db, _, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + return db + }, + }, + { + name: "duplicate slotname in publisher slotnamehash", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetSlotNameHash: "^SELECT (.+) FROM wrapper_publisher_slot (.+)", + }, + }, + }, + args: args{ + pubID: 5890, + }, + want: map[string]string{ + "/43743431/DMDemo1@160x600": "2fb84286ede5b20e82b0601df0c7e454", + "/43743431/DMDemo2@160x600": "2aa34b52a9e941c1594af7565e599c8d", + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"name", "hash"}). + AddRow("/43743431/DMDemo1@160x600", "eb15e9be2d65f0268ff498572d3bb53e"). + AddRow("/43743431/DMDemo1@160x600", "f514eb9f174485f850b7e92d2a40baf6"). + AddRow("/43743431/DMDemo1@160x600", "2fb84286ede5b20e82b0601df0c7e454"). + AddRow("/43743431/DMDemo2@160x600", "2aa34b52a9e941c1594af7565e599c8d") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_slot (.+)")).WillReturnRows(rows) + + return db + }, + }, + { + name: "valid publisher slotnamehash", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetSlotNameHash: "^SELECT (.+) FROM wrapper_publisher_slot (.+)", + }, + }, + }, + args: args{ + pubID: 5890, + }, + want: map[string]string{ + "/43743431/DMDemo1@160x600": "2fb84286ede5b20e82b0601df0c7e454", + "/43743431/DMDemo2@160x600": "2aa34b52a9e941c1594af7565e599c8d", + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"name", "hash"}). + AddRow("/43743431/DMDemo1@160x600", "2fb84286ede5b20e82b0601df0c7e454"). + AddRow("/43743431/DMDemo2@160x600", "2aa34b52a9e941c1594af7565e599c8d") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_slot (.+)")).WillReturnRows(rows) + + return db + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &mySqlDB{ + conn: tt.setup(), + cfg: tt.fields.cfg, + } + got, err := db.GetPublisherSlotNameHash(tt.args.pubID) + if (err != nil) != tt.wantErr { + t.Errorf("mySqlDB.GetPublisherSlotNameHash() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_mySqlDB_GetWrapperSlotMappings(t *testing.T) { + type fields struct { + cfg config.Database + } + type args struct { + partnerConfigMap map[int]map[string]string + profileID int + displayVersion int + } + tests := []struct { + name string + fields fields + args args + want map[int][]models.SlotMapping + wantErr bool + setup func() *sql.DB + }{ + { + name: "empty query in config file", + want: map[int][]models.SlotMapping{}, + wantErr: true, + setup: func() *sql.DB { + db, _, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + return db + }, + }, + { + name: "invalid partnerId in wrapper slot mapping with displayversion 0", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetWrapperLiveVersionSlotMappings: "^SELECT (.+) FROM wrapper_partner_slot_mapping (.+) LIVE", + }, + }, + }, + args: args{ + partnerConfigMap: formTestPartnerConfig(), + profileID: 19109, + displayVersion: 0, + }, + want: map[int][]models.SlotMapping{ + 10: { + { + PartnerId: 10, + AdapterId: 1, + VersionId: 1, + SlotName: "/43743431/DMDemo2@160x600", + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\"}", + SlotMappings: nil, + Hash: "", + OrderID: 0, + }, + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"PartnerId", "AdapterId", "VersionId", "SlotName", "MappingJson", "OrderId"}). + AddRow("10_112", 1, 1, "/43743431/DMDemo1@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0). + AddRow(10, 1, 1, "/43743431/DMDemo2@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_partner_slot_mapping (.+) LIVE")).WillReturnRows(rows) + + return db + }, + }, + { + name: "valid wrapper slot mapping with displayversion 0", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetWrapperLiveVersionSlotMappings: "^SELECT (.+) FROM wrapper_partner_slot_mapping (.+) LIVE", + }, + }, + }, + args: args{ + partnerConfigMap: formTestPartnerConfig(), + profileID: 19109, + displayVersion: 0, + }, + want: map[int][]models.SlotMapping{ + 10: { + { + PartnerId: 10, + AdapterId: 1, + VersionId: 1, + SlotName: "/43743431/DMDemo1@160x600", + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\"}", + SlotMappings: nil, + Hash: "", + OrderID: 0, + }, + { + PartnerId: 10, + AdapterId: 1, + VersionId: 1, + SlotName: "/43743431/DMDemo2@160x600", + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\"}", + SlotMappings: nil, + Hash: "", + OrderID: 0, + }, + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"PartnerId", "AdapterId", "VersionId", "SlotName", "MappingJson", "OrderId"}). + AddRow(10, 1, 1, "/43743431/DMDemo1@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0). + AddRow(10, 1, 1, "/43743431/DMDemo2@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_partner_slot_mapping (.+) LIVE")).WillReturnRows(rows) + + return db + }, + }, + { + name: "valid wrapper slot mapping with displayversion non-zero", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetWrapperSlotMappingsQuery: "^SELECT (.+) FROM wrapper_partner_slot_mapping (.+)", + }, + }, + }, + args: args{ + partnerConfigMap: formTestPartnerConfig(), + profileID: 19109, + displayVersion: 4, + }, + want: map[int][]models.SlotMapping{ + 10: { + { + PartnerId: 10, + AdapterId: 1, + VersionId: 1, + SlotName: "/43743431/DMDemo1@160x600", + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\"}", + SlotMappings: nil, + Hash: "", + OrderID: 0, + }, + { + PartnerId: 10, + AdapterId: 1, + VersionId: 1, + SlotName: "/43743431/DMDemo2@160x600", + MappingJson: "{\"adtag\":\"1405192\",\"site\":\"47124\"}", + SlotMappings: nil, + Hash: "", + OrderID: 0, + }, + }, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"PartnerId", "AdapterId", "VersionId", "SlotName", "MappingJson", "OrderId"}). + AddRow(10, 1, 1, "/43743431/DMDemo1@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0). + AddRow(10, 1, 1, "/43743431/DMDemo2@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_partner_slot_mapping (.+)")).WillReturnRows(rows) + + return db + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &mySqlDB{ + conn: tt.setup(), + cfg: tt.fields.cfg, + } + got, err := db.GetWrapperSlotMappings(tt.args.partnerConfigMap, tt.args.profileID, tt.args.displayVersion) + if (err != nil) != tt.wantErr { + t.Errorf("mySqlDB.GetWrapperSlotMappings() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_mySqlDB_GetMappings(t *testing.T) { + type args struct { + slotKey string + slotMap map[string]models.SlotMapping + } + tests := []struct { + name string + args args + want map[string]interface{} + wantErr bool + }{ + { + name: "empty_data", + args: args{}, + want: nil, + wantErr: true, + }, + { + name: "slotmapping_notfound", + args: args{ + slotKey: "key1", + slotMap: map[string]models.SlotMapping{ + "slot1": {}, + }, + }, + want: nil, + wantErr: true, + }, + { + name: "slotmapping_found_with_empty_fieldmap", + args: args{ + slotKey: "slot1", + slotMap: map[string]models.SlotMapping{ + "slot1": {}, + }, + }, + want: nil, + wantErr: false, + }, + { + name: "slotmapping_found_with_fieldmap", + args: args{ + slotKey: "slot1", + slotMap: map[string]models.SlotMapping{ + "slot1": { + SlotMappings: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + }, + }, + }, + }, + want: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + }, + wantErr: false, + }, + { + name: "key_case_sensitive", + args: args{ + slotKey: "SLOT1", + slotMap: map[string]models.SlotMapping{ + "slot1": { + SlotMappings: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + }, + }, + }, + }, + want: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &mySqlDB{} + got, err := db.GetMappings(tt.args.slotKey, tt.args.slotMap) + if (err != nil) != tt.wantErr { + t.Errorf("mySqlDB.GetMappings() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func formTestPartnerConfig() map[int]map[string]string { + + partnerConfigMap := make(map[int]map[string]string) + + partnerConfigMap[0] = map[string]string{ + "partnerId": "10", + "prebidPartnerName": "pubmatic", + "serverSideEnabled": "1", + "level": "multi", + "kgp": "_AU_@_W_x_H", + "timeout": "220", + } + + return partnerConfigMap +} diff --git a/modules/pubmatic/openwrap/database/mysql/tracking_beacon_first.go b/modules/pubmatic/openwrap/database/mysql/tracking_beacon_first.go new file mode 100644 index 00000000000..2d6e298fa57 --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/tracking_beacon_first.go @@ -0,0 +1,40 @@ +// Package mysql provides functionalities to interact with the giym database. +// This file is used for retrieving and managing data related to the tracking-beacon-first (TBF) feature for publishers. +// This includes methods to fetch and process tracking-beacon-first traffic details associated +// with publisher IDs from the giym database. +package mysql + +import ( + "encoding/json" + + "github.com/golang/glog" +) + +// GetTBFTrafficForPublishers function fetches the publisher data for TBF (tracking-beacon-first) feature from database +func (db *mySqlDB) GetTBFTrafficForPublishers() (map[int]map[int]int, error) { + rows, err := db.conn.Query(db.cfg.Queries.GetTBFRateQuery) + if err != nil { + return nil, err + } + defer rows.Close() + + pubProfileTrafficRate := make(map[int]map[int]int) + for rows.Next() { + var pubID int + var trafficDetails string + + if err := rows.Scan(&pubID, &trafficDetails); err != nil { + glog.Error("ErrRowScanFailed GetTBFRateQuery pubid: ", pubID, " err: ", err.Error()) + continue + } + + // convert trafficDetails into map[profileId]traffic + var profileTrafficRate map[int]int + if err := json.Unmarshal([]byte(trafficDetails), &profileTrafficRate); err != nil { + glog.Error("ErrJSONUnmarshalFailed TBFProfileTrafficRate pubid: ", pubID, " trafficDetails: ", trafficDetails, " err: ", err.Error()) + continue + } + pubProfileTrafficRate[pubID] = profileTrafficRate + } + return pubProfileTrafficRate, nil +} diff --git a/modules/pubmatic/openwrap/database/mysql/tracking_beacon_first_test.go b/modules/pubmatic/openwrap/database/mysql/tracking_beacon_first_test.go new file mode 100644 index 00000000000..a3147b7e530 --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/tracking_beacon_first_test.go @@ -0,0 +1,167 @@ +package mysql + +import ( + "database/sql" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/assert" +) + +func TestGetTBFTrafficForPublishers(t *testing.T) { + type want struct { + trafficDetails map[int]map[int]int + err error + } + + tests := []struct { + name string + setup func(db *mySqlDB) + want want + }{ + { + name: "db_query_fail", + setup: func(db *mySqlDB) { + conn, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + rows := sqlmock.NewRows([]string{"value"}) + rows.AddRow("{'5890': 12}") + mock.ExpectQuery("").WillReturnError(sql.ErrConnDone) + db.conn = conn + }, + want: want{ + trafficDetails: nil, + err: sql.ErrConnDone, + }, + }, + { + name: "query_returns_empty_rows", + setup: func(db *mySqlDB) { + conn, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + rows := sqlmock.NewRows([]string{"value"}) + mock.ExpectQuery("").WillReturnRows(rows) + db.conn = conn + }, + want: want{ + trafficDetails: map[int]map[int]int{}, + err: nil, + }, + }, + { + name: "row_scan_failure", + setup: func(db *mySqlDB) { + conn, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + row := mock.NewRows([]string{"value"}).AddRow(nil) + mock.ExpectQuery("").WillReturnRows(row) + db.conn = conn + }, + want: want{ + trafficDetails: map[int]map[int]int{}, + err: nil, + }, + }, + { + name: "json_unmarshal_fail", + setup: func(db *mySqlDB) { + conn, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + row := mock.NewRows([]string{"pubid", "value"}).AddRow("5890", "{1234:10}") + mock.ExpectQuery("").WillReturnRows(row) + db.conn = conn + }, + want: want{ + trafficDetails: map[int]map[int]int{}, + err: nil, + }, + }, + { + name: "valid_single_row", + setup: func(db *mySqlDB) { + conn, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + row := mock.NewRows([]string{"pubid", "value"}).AddRow("5890", "{\"1234\":10}") + mock.ExpectQuery("").WillReturnRows(row) + db.conn = conn + }, + want: want{ + trafficDetails: map[int]map[int]int{ + 5890: {1234: 10}, + }, + err: nil, + }, + }, + { + name: "multi_row_response", + setup: func(db *mySqlDB) { + conn, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + row := mock.NewRows([]string{"pubid", "value"}) + row.AddRow("5890", "{\"1234\":10 ,\"4321\": 90}") + row.AddRow("5891", "{\"5678\":20}") + mock.ExpectQuery("").WillReturnRows(row) + db.conn = conn + }, + want: want{ + trafficDetails: map[int]map[int]int{ + 5890: {1234: 10, 4321: 90}, + 5891: {5678: 20}, + }, + err: nil, + }, + }, + { + name: "one_invalid_row_in_multi_row_response", + setup: func(db *mySqlDB) { + conn, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + row := mock.NewRows([]string{"pubid", "value"}) + row.AddRow("5890", "{\"1234\":10}") + row.AddRow("5890", "invalid_row") + row.AddRow("5890", "{\"5678\":20}") + + mock.ExpectQuery("").WillReturnRows(row) + db.conn = conn + }, + want: want{ + trafficDetails: map[int]map[int]int{ + 5890: {5678: 20}, + }, + err: nil, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mySQLDB := mySqlDB{} + tt.setup(&mySQLDB) + + trafficDetails, err := mySQLDB.GetTBFTrafficForPublishers() + assert.Equalf(t, tt.want.trafficDetails, trafficDetails, tt.name) + assert.Equalf(t, tt.want.err, err, tt.name) + }) + } +} diff --git a/modules/pubmatic/openwrap/database/mysql/vasttags.go b/modules/pubmatic/openwrap/database/mysql/vasttags.go new file mode 100644 index 00000000000..6b036ac00a5 --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/vasttags.go @@ -0,0 +1,35 @@ +package mysql + +import ( + "fmt" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +// GetPublisherVASTTags - Method to get vast tags associated with publisher id from giym DB +func (db *mySqlDB) GetPublisherVASTTags(pubID int) (models.PublisherVASTTags, error) { + + /* + //TOOD:VIRAL Remove Hook once UI/API changes are in place + if out := vastTagHookPublisherVASTTags(rtbReqId, pubID); nil != out { + return out, nil + } + */ + + getActiveVASTTagsQuery := fmt.Sprintf(db.cfg.Queries.GetPublisherVASTTagsQuery, pubID) + + rows, err := db.conn.Query(getActiveVASTTagsQuery) + if err != nil { + return nil, err + } + defer rows.Close() + + vasttags := models.PublisherVASTTags{} + for rows.Next() { + var vastTag models.VASTTag + if err := rows.Scan(&vastTag.ID, &vastTag.PartnerID, &vastTag.URL, &vastTag.Duration, &vastTag.Price); err == nil { + vasttags[vastTag.ID] = &vastTag + } + } + return vasttags, nil +} diff --git a/modules/pubmatic/openwrap/database/mysql/vasttags_test.go b/modules/pubmatic/openwrap/database/mysql/vasttags_test.go new file mode 100644 index 00000000000..7e8b183d951 --- /dev/null +++ b/modules/pubmatic/openwrap/database/mysql/vasttags_test.go @@ -0,0 +1,117 @@ +package mysql + +import ( + "database/sql" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/stretchr/testify/assert" +) + +func Test_mySqlDB_GetPublisherVASTTags(t *testing.T) { + type fields struct { + cfg config.Database + } + type args struct { + pubID int + } + tests := []struct { + name string + fields fields + args args + want models.PublisherVASTTags + wantErr bool + setup func() *sql.DB + }{ + { + name: "empty query in config file", + want: nil, + wantErr: true, + setup: func() *sql.DB { + db, _, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + return db + }, + }, + { + name: "invalid vast tag partnerId", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetPublisherVASTTagsQuery: "^SELECT (.+) FROM wrapper_publisher_partner_vast_tag (.+)", + }, + }, + }, + args: args{ + pubID: 5890, + }, + want: models.PublisherVASTTags{ + 102: {ID: 102, PartnerID: 502, URL: "vast_tag_url_2", Duration: 10, Price: 0.0}, + 103: {ID: 103, PartnerID: 501, URL: "vast_tag_url_1", Duration: 30, Price: 3.0}, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"id", "partnerId", "url", "duration", "price"}). + AddRow(101, "501_12", "vast_tag_url_1", 15, 2.0). + AddRow(102, 502, "vast_tag_url_2", 10, 0.0). + AddRow(103, 501, "vast_tag_url_1", 30, 3.0) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_partner_vast_tag (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "valid vast tags", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetPublisherVASTTagsQuery: "^SELECT (.+) FROM wrapper_publisher_partner_vast_tag (.+)", + }, + }, + }, + args: args{ + pubID: 5890, + }, + want: models.PublisherVASTTags{ + 101: {ID: 101, PartnerID: 501, URL: "vast_tag_url_1", Duration: 15, Price: 2.0}, + 102: {ID: 102, PartnerID: 502, URL: "vast_tag_url_2", Duration: 10, Price: 0.0}, + 103: {ID: 103, PartnerID: 501, URL: "vast_tag_url_1", Duration: 30, Price: 3.0}, + }, + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"id", "partnerId", "url", "duration", "price"}). + AddRow(101, 501, "vast_tag_url_1", 15, 2.0). + AddRow(102, 502, "vast_tag_url_2", 10, 0.0). + AddRow(103, 501, "vast_tag_url_1", 30, 3.0) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_partner_vast_tag (.+)")).WillReturnRows(rows) + return db + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &mySqlDB{ + conn: tt.setup(), + cfg: tt.fields.cfg, + } + got, err := db.GetPublisherVASTTags(tt.args.pubID) + if (err != nil) != tt.wantErr { + t.Errorf("mySqlDB.GetPublisherVASTTags() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/defaultbids.go b/modules/pubmatic/openwrap/defaultbids.go new file mode 100644 index 00000000000..07e3a83cbdf --- /dev/null +++ b/modules/pubmatic/openwrap/defaultbids.go @@ -0,0 +1,205 @@ +package openwrap + +import ( + "encoding/json" + "strconv" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adunitconfig" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + uuid "github.com/satori/go.uuid" +) + +func (m *OpenWrap) addDefaultBids(rctx *models.RequestCtx, bidResponse *openrtb2.BidResponse, bidResponseExt openrtb_ext.ExtBidResponse) map[string]map[string][]openrtb2.Bid { + // responded bidders per impression + seatBids := make(map[string]map[string]struct{}, len(bidResponse.SeatBid)) + for _, seatBid := range bidResponse.SeatBid { + for _, bid := range seatBid.Bid { + if seatBids[bid.ImpID] == nil { + seatBids[bid.ImpID] = make(map[string]struct{}) + } + seatBids[bid.ImpID][seatBid.Seat] = struct{}{} + } + } + + // consider responded but dropped bids to avoid false nobid entries + for seat, bids := range rctx.DroppedBids { + for _, bid := range bids { + if seatBids[bid.ImpID] == nil { + seatBids[bid.ImpID] = make(map[string]struct{}) + } + seatBids[bid.ImpID][seat] = struct{}{} + } + } + + // bids per bidders per impression that did not respond + defaultBids := make(map[string]map[string][]openrtb2.Bid, 0) + for impID, impCtx := range rctx.ImpBidCtx { + for bidder := range impCtx.Bidders { + if bidders, ok := seatBids[impID]; ok { // bid found for impID + if _, ok := bidders[bidder]; ok { // bid found for seat + continue + } + } + + if defaultBids[impID] == nil { + defaultBids[impID] = make(map[string][]openrtb2.Bid) + } + + uuid := uuid.NewV4().String() + bidExt := newDefaultBidExt(*rctx, impID, bidder, bidResponseExt) + bidExtJson, _ := json.Marshal(bidExt) + + defaultBids[impID][bidder] = append(defaultBids[impID][bidder], openrtb2.Bid{ + ID: uuid, + ImpID: impID, + Ext: bidExtJson, + }) + + // create bidCtx because we need it for owlogger + rctx.ImpBidCtx[impID].BidCtx[uuid] = models.BidCtx{ + BidExt: models.BidExt{ + Nbr: bidExt.Nbr, + }, + } + + // record error stats for each bidder + m.recordErrorStats(*rctx, bidResponseExt, bidder) + } + } + + // add nobids for throttled adapter to all the impressions (how do we set profile with custom list of bidders at impression level?) + for bidder := range rctx.AdapterThrottleMap { + for impID := range rctx.ImpBidCtx { // ImpBidCtx is used only for list of impID, it does not have data of throttled adapters + if defaultBids[impID] == nil { + defaultBids[impID] = make(map[string][]openrtb2.Bid) + } + + bidExt := newDefaultBidExt(*rctx, impID, bidder, bidResponseExt) + bidExtJson, _ := json.Marshal(bidExt) + // no need to create impBidCtx since we dont log partner-throttled bid in owlogger + + defaultBids[impID][bidder] = []openrtb2.Bid{ + { + ID: uuid.NewV4().String(), + ImpID: impID, + Ext: bidExtJson, + }, + } + } + } + + // add nobids for non-mapped bidders + for impID, impCtx := range rctx.ImpBidCtx { + for bidder := range impCtx.NonMapped { + if defaultBids[impID] == nil { + defaultBids[impID] = make(map[string][]openrtb2.Bid) + } + + bidExt := newDefaultBidExt(*rctx, impID, bidder, bidResponseExt) + bidExtJson, _ := json.Marshal(bidExt) + // no need to create impBidCtx since we dont log slot-not-mapped bid in owlogger + + defaultBids[impID][bidder] = []openrtb2.Bid{ + { + ID: uuid.NewV4().String(), + ImpID: impID, + Ext: bidExtJson, + }, + } + } + } + + return defaultBids +} + +// getNonBRCodeFromBidRespExt maps the error-code present in prebid partner response with standard nonBR code +func getNonBRCodeFromBidRespExt(bidder string, bidResponseExt openrtb_ext.ExtBidResponse) *openrtb3.NonBidStatusCode { + errs := bidResponseExt.Errors[openrtb_ext.BidderName(bidder)] + if len(errs) == 0 { + return GetNonBidStatusCodePtr(openrtb3.NoBidGeneral) + } + + switch errs[0].Code { + case errortypes.TimeoutErrorCode: + return GetNonBidStatusCodePtr(openrtb3.NoBidTimeoutError) + case errortypes.UnknownErrorCode: + return GetNonBidStatusCodePtr(openrtb3.NoBidGeneralError) + default: + return GetNonBidStatusCodePtr(openrtb3.NoBidGeneralError) + } +} + +func newDefaultBidExt(rctx models.RequestCtx, impID, bidder string, bidResponseExt openrtb_ext.ExtBidResponse) *models.BidExt { + + bidExt := models.BidExt{ + NetECPM: 0, + Nbr: getNonBRCodeFromBidRespExt(bidder, bidResponseExt), + } + if rctx.ClientConfigFlag == 1 { + if cc := adunitconfig.GetClientConfigForMediaType(rctx, impID, "banner"); cc != nil { + bidExt.Banner = &models.ExtBidBanner{ + ClientConfig: cc, + } + } + + if cc := adunitconfig.GetClientConfigForMediaType(rctx, impID, "video"); cc != nil { + bidExt.Video = &models.ExtBidVideo{ + ClientConfig: cc, + } + } + } + + if v, ok := rctx.PartnerConfigMap[models.VersionLevelConfigID]["refreshInterval"]; ok { + n, err := strconv.Atoi(v) + if err == nil { + bidExt.RefreshInterval = n + } + } + return &bidExt +} + +func (m *OpenWrap) applyDefaultBids(rctx models.RequestCtx, bidResponse *openrtb2.BidResponse) (*openrtb2.BidResponse, error) { + // update nobids in final response + for i, seatBid := range bidResponse.SeatBid { + for impID, noSeatBid := range rctx.DefaultBids { + for seat, bids := range noSeatBid { + if seatBid.Seat == seat { + bidResponse.SeatBid[i].Bid = append(bidResponse.SeatBid[i].Bid, bids...) + delete(noSeatBid, seat) + rctx.DefaultBids[impID] = noSeatBid + } + } + } + } + + // no-seat case + for _, noSeatBid := range rctx.DefaultBids { + for seat, bids := range noSeatBid { + bidResponse.SeatBid = append(bidResponse.SeatBid, openrtb2.SeatBid{ + Bid: bids, + Seat: seat, + }) + } + } + + return bidResponse, nil +} +func (m *OpenWrap) recordErrorStats(rctx models.RequestCtx, bidResponseExt openrtb_ext.ExtBidResponse, bidder string) { + + responseError := models.PartnerErrNoBid + + bidderErr, ok := bidResponseExt.Errors[openrtb_ext.BidderName(bidder)] + if ok && len(bidderErr) > 0 { + switch bidderErr[0].Code { + case errortypes.TimeoutErrorCode: + responseError = models.PartnerErrTimeout + case errortypes.UnknownErrorCode: + responseError = models.PartnerErrUnknownPrebidError + } + } + m.metricEngine.RecordPartnerResponseErrors(rctx.PubIDStr, bidder, responseError) +} diff --git a/modules/pubmatic/openwrap/defaultbids_test.go b/modules/pubmatic/openwrap/defaultbids_test.go new file mode 100644 index 00000000000..ba63a06ec5c --- /dev/null +++ b/modules/pubmatic/openwrap/defaultbids_test.go @@ -0,0 +1,87 @@ +package openwrap + +import ( + "testing" + + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestGetNonBRCodeFromBidRespExt(t *testing.T) { + type args struct { + bidder string + bidResponseExt openrtb_ext.ExtBidResponse + } + tests := []struct { + name string + args args + nbr *openrtb3.NonBidStatusCode + }{ + { + name: "bidResponseExt.Errors_is_empty", + args: args{ + bidder: "pubmatic", + bidResponseExt: openrtb_ext.ExtBidResponse{ + Errors: nil, + }, + }, + nbr: GetNonBidStatusCodePtr(openrtb3.NoBidGeneral), + }, + { + name: "invalid_partner_err", + args: args{ + bidder: "pubmatic", + bidResponseExt: openrtb_ext.ExtBidResponse{ + Errors: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ + "pubmatic": { + { + Code: 0, + }, + }, + }, + }, + }, + nbr: GetNonBidStatusCodePtr(openrtb3.NoBidGeneralError), + }, + { + name: "unknown_partner_err", + args: args{ + bidder: "pubmatic", + bidResponseExt: openrtb_ext.ExtBidResponse{ + Errors: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ + "pubmatic": { + { + Code: errortypes.UnknownErrorCode, + }, + }, + }, + }, + }, + nbr: GetNonBidStatusCodePtr(openrtb3.NoBidGeneralError), + }, + { + name: "partner_timeout_err", + args: args{ + bidder: "pubmatic", + bidResponseExt: openrtb_ext.ExtBidResponse{ + Errors: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ + "pubmatic": { + { + Code: errortypes.TimeoutErrorCode, + }, + }, + }, + }, + }, + nbr: GetNonBidStatusCodePtr(openrtb3.NoBidTimeoutError), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nbr := getNonBRCodeFromBidRespExt(tt.args.bidder, tt.args.bidResponseExt) + assert.Equal(t, tt.nbr, nbr, tt.name) + }) + } +} diff --git a/modules/pubmatic/openwrap/device.go b/modules/pubmatic/openwrap/device.go new file mode 100644 index 00000000000..a01170819a0 --- /dev/null +++ b/modules/pubmatic/openwrap/device.go @@ -0,0 +1,48 @@ +package openwrap + +import ( + "encoding/json" + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func validateDevice(device *openrtb2.Device) { + //unmarshal device ext + var deviceExt models.ExtDevice + err := json.Unmarshal(device.Ext, &deviceExt) + if err != nil { + return + } + + if deviceExt.ExtDevice != nil { + deviceExt.IFAType = strings.TrimSpace(deviceExt.IFAType) + deviceExt.SessionID = strings.TrimSpace(deviceExt.SessionID) + + //refactor below condition + if deviceExt.IFAType != "" { + if device.IFA != "" { + if _, ok := models.DeviceIFATypeID[strings.ToLower(deviceExt.IFAType)]; !ok { + deviceExt.IFAType = "" + } + } else if deviceExt.SessionID != "" { + device.IFA = deviceExt.SessionID + deviceExt.IFAType = models.DeviceIFATypeSESSIONID + } else { + deviceExt.IFAType = "" + } + } else if deviceExt.SessionID != "" { + device.IFA = deviceExt.SessionID + deviceExt.IFAType = models.DeviceIFATypeSESSIONID + } + } else if deviceExt.SessionID != "" { + deviceExt.ExtDevice = &openrtb_ext.ExtDevice{ + IFAType: models.DeviceIFATypeSESSIONID, + } + device.IFA = deviceExt.SessionID + } + + device.Ext, _ = json.Marshal(deviceExt) +} diff --git a/modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25/v25.go b/modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25/v25.go new file mode 100644 index 00000000000..5118119c571 --- /dev/null +++ b/modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25/v25.go @@ -0,0 +1 @@ +package v25 diff --git a/modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25/video.go b/modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25/video.go new file mode 100644 index 00000000000..1c895292382 --- /dev/null +++ b/modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25/video.go @@ -0,0 +1,716 @@ +package v25 + +import ( + "encoding/json" + "errors" + "math/rand" + "net/url" + "strconv" + "strings" + + "github.com/gofrs/uuid" + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func ConvertVideoToAuctionRequest(payload hookstage.EntrypointPayload, result *hookstage.HookResult[hookstage.EntrypointPayload]) (models.RequestExtWrapper, error) { + values := payload.Request.URL.Query() + + pubID := values.Get(models.PUBID_KEY) + profileID := values.Get(models.PROFILEID_KEY) + + owRedirectURLStr := values.Get(models.OWRedirectURLKey) + // mimeTypesStr := values.Get(models.MimeTypes) + gdprFlag := values.Get(models.GDPRFlag) + ccpa := values.Get(models.CCPAUSPrivacyKey) + eids := values.Get(models.OWUserEids) + consentString := values.Get(models.ConsentString) + appReq := values.Get(models.AppRequest) + responseFormat := values.Get(models.ResponseFormatKey) + + if owRedirectURLStr == "" && responseFormat != models.ResponseFormatJSON { + return models.RequestExtWrapper{}, errors.New(models.OWRedirectURLKey + " missing in request") + } + + redirectURL, err := url.Parse(owRedirectURLStr) + if err != nil { + return models.RequestExtWrapper{}, errors.New(models.OWRedirectURLKey + "url parsing failed") + } + redirectQueryParams := redirectURL.Query() + // Replace macro values in DFP URL - NYC TODO: Do we still need to trim the macro prefix? + for k := range values { + if strings.HasPrefix(k, models.MacroPrefix) { + paramName := strings.TrimPrefix(k, models.MacroPrefix) + redirectQueryParams.Set(paramName, values.Get(k)) + } + } + + bidRequest := openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + Video: &openrtb2.Video{ + MIMEs: GetStringArr(GetValueFromRequest(values, redirectQueryParams, models.MimeORTBParam)), + MaxDuration: GetCustomAtoI64(GetString(GetValueFromRequest(values, redirectQueryParams, models.MaxDurationORTBParam))), + MinDuration: GetCustomAtoI64(GetString(GetValueFromRequest(values, redirectQueryParams, models.MinDurationORTBParam))), + Protocols: models.GetProtocol(GetIntArr(GetValueFromRequest(values, redirectQueryParams, models.ProtocolsORTBParam))), + Skip: GetCustomAtoI8(GetString(GetValueFromRequest(values, redirectQueryParams, models.SkipORTBParam))), + SkipMin: GetCustomAtoI64(GetString(GetValueFromRequest(values, redirectQueryParams, models.SkipMinORTBParam))), + SkipAfter: GetCustomAtoI64(GetString(GetValueFromRequest(values, redirectQueryParams, models.SkipAfterORTBParam))), + BAttr: models.GetCreativeAttributes(GetIntArr(GetValueFromRequest(values, redirectQueryParams, models.BAttrORTBParam))), + MaxExtended: GetCustomAtoI64(GetString(GetValueFromRequest(values, redirectQueryParams, models.MaxExtendedORTBParam))), + MinBitRate: GetCustomAtoI64(GetString(GetValueFromRequest(values, redirectQueryParams, models.MinBitrateORTBParam))), + MaxBitRate: GetCustomAtoI64(GetString(GetValueFromRequest(values, redirectQueryParams, models.MaxBitrateORTBParam))), + PlaybackMethod: models.GetPlaybackMethod(GetIntArr(GetValueFromRequest(values, redirectQueryParams, models.PlaybackMethodORTBParam))), + Delivery: models.GetDeliveryMethod(GetIntArr(GetValueFromRequest(values, redirectQueryParams, models.DeliveryORTBParam))), + API: models.GetAPIFramework((GetIntArr(GetValueFromRequest(values, redirectQueryParams, models.APIORTBParam)))), + }, + }, + }, + } + + if sequence := GetCustomAtoI8(GetString(GetValueFromRequest(values, redirectQueryParams, models.SequenceORTBParam))); sequence != nil { + bidRequest.Imp[0].Video.Sequence = *sequence + } + if boxingAllowed := GetCustomAtoI8(GetString(GetValueFromRequest(values, redirectQueryParams, models.BoxingAllowedORTBParam))); boxingAllowed != nil { + bidRequest.Imp[0].Video.BoxingAllowed = *boxingAllowed + } + if prctl := GetCustomAtoI8(GetString(GetValueFromRequest(values, redirectQueryParams, models.ProtocolORTBParam))); prctl != nil { + bidRequest.Imp[0].Video.Protocol = adcom1.MediaCreativeSubtype(*prctl) + } + if strtDelay := GetCustomAtoI64(GetString(GetValueFromRequest(values, redirectQueryParams, models.StartDelayORTBParam))); strtDelay != 0 { + st := adcom1.StartDelay(strtDelay) + bidRequest.Imp[0].Video.StartDelay = &st + } + if placementValue := GetCustomAtoI64(GetString(GetValueFromRequest(values, redirectQueryParams, models.PlacementORTBParam))); placementValue != 0 { + bidRequest.Imp[0].Video.Placement = adcom1.VideoPlacementSubtype(placementValue) + } + if linearityValue := GetCustomAtoI64(GetString(GetValueFromRequest(values, redirectQueryParams, models.LinearityORTBParam))); linearityValue != 0 { + bidRequest.Imp[0].Video.Linearity = adcom1.LinearityMode(linearityValue) + } + if pos := GetCustomAtoI8(GetString(GetValueFromRequest(values, redirectQueryParams, models.PosORTBParam))); pos != nil { + pos := adcom1.PlacementPosition(*pos) + bidRequest.Imp[0].Video.Pos = &pos + } + + size := GetString(GetValueFromRequest(values, redirectQueryParams, models.SizeORTBParam)) + if size != "" && strings.Split(size, "x") != nil { + sizeValues := strings.Split(size, "x") + bidRequest.Imp[0].Video.W, _ = strconv.ParseInt(sizeValues[0], 10, 64) + bidRequest.Imp[0].Video.H, _ = strconv.ParseInt(sizeValues[1], 10, 64) + } + + slot := redirectQueryParams.Get(models.InventoryUnitKey) + if slot == "" && responseFormat == models.ResponseFormatJSON { + slot = values.Get(models.InventoryUnitMacroKey) + } + + validationFailed := false + if slot == "" { + validationFailed = true + } + + // TODO NYC: do we need this?? + // if mimeTypesStr == "" { + // validationFailed = true + // } else { + // mimeStrArr := strings.Split(values.Get(models.OWMimeTypes), models.MimesSeparator) + // if len(mimeStrArr) == 0 { + // validationFailed = true + // } else { + // for _, mime := range mimeStrArr { + // if models.MimeIDToValueMap[mime] == "" { + // validationFailed = true + // break + // } + // } + // } + // } + + // if gdprFlag != "" && gdprFlag != "0" && gdprFlag != "1" { + // validationFailed = true + // } + + // if ccpa != "" && len(ccpa) != 4 { + // validationFailed = true + // } + + // request is for Mobile App, perform necessary validations + if appReq == "1" && (models.CheckIfValidQueryParamFlag(values, models.DeviceLMT) || models.CheckIfValidQueryParamFlag(values, models.DeviceDNT)) { + validationFailed = true + } + + if validationFailed { + return models.RequestExtWrapper{}, errors.New("validation failure") + } + + if uuid, err := uuid.NewV4(); err == nil { + bidRequest.ID = uuid.String() + } + + if uuid, err := uuid.NewV4(); err == nil { + bidRequest.Imp[0].ID = uuid.String() + } + bidRequest.Imp[0].TagID = slot + bidRequest.Imp[0].BidFloor, _ = models.Atof(values.Get(models.FloorValue), 4) + bidRequest.Imp[0].BidFloorCur = values.Get(models.FloorCurrency) + + content := &openrtb2.Content{ + Genre: GetString(GetValueFromRequest(values, redirectQueryParams, models.ContentGenreORTBParam)), + Title: GetString(GetValueFromRequest(values, redirectQueryParams, models.ContentTitleORTBParam)), + } + if content.Genre == "" && content.Title == "" { + content = nil + } + + if appReq == "1" { + bidRequest.App = &openrtb2.App{ + ID: GetString(GetValueFromRequest(values, redirectQueryParams, models.AppIDORTBParam)), + Name: GetString(GetValueFromRequest(values, redirectQueryParams, models.AppNameORTBParam)), + Bundle: GetString(GetValueFromRequest(values, redirectQueryParams, models.AppBundleORTBParam)), + StoreURL: GetString(GetValueFromRequest(values, redirectQueryParams, models.AppStoreURLORTBParam)), + Domain: GetString(GetValueFromRequest(values, redirectQueryParams, models.AppDomainORTBParam)), + Keywords: GetString(GetValueFromRequest(values, redirectQueryParams, models.OwAppKeywords)), + Cat: GetStringArr(GetValueFromRequest(values, redirectQueryParams, models.AppCatORTBParam)), + Publisher: &openrtb2.Publisher{ + ID: pubID, + }, + Content: content, + } + bidRequest.Device = &openrtb2.Device{ + Lmt: GetCustomAtoI8(GetString(GetValueFromRequest(values, redirectQueryParams, models.DeviceLMTORTBParam))), + DNT: GetCustomAtoI8(GetString(GetValueFromRequest(values, redirectQueryParams, models.DeviceDNTORTBParam))), + IFA: GetString(GetValueFromRequest(values, redirectQueryParams, models.DeviceIfaORTBParam)), + DIDSHA1: GetString(GetValueFromRequest(values, redirectQueryParams, models.DeviceDidsha1ORTBParam)), + DIDMD5: GetString(GetValueFromRequest(values, redirectQueryParams, models.DeviceDidmd5ORTBParam)), + DPIDSHA1: GetString(GetValueFromRequest(values, redirectQueryParams, models.DeviceDpidsha1ORTBParam)), + DPIDMD5: GetString(GetValueFromRequest(values, redirectQueryParams, models.DeviceDpidmd5ORTBParam)), + MACSHA1: GetString(GetValueFromRequest(values, redirectQueryParams, models.DeviceMacsha1ORTBParam)), + MACMD5: GetString(GetValueFromRequest(values, redirectQueryParams, models.DeviceMacmd5ORTBParam)), + Geo: &openrtb2.Geo{ + Lat: GetCustomStrToFloat(GetString(GetValueFromRequest(values, redirectQueryParams, models.GeoLatORTBParam))), + Lon: GetCustomStrToFloat(GetString(GetValueFromRequest(values, redirectQueryParams, models.GeoLonORTBParam))), + Country: GetString(GetValueFromRequest(values, redirectQueryParams, models.GeoCountryORTBParam)), + City: GetString(GetValueFromRequest(values, redirectQueryParams, models.GeoCityORTBParam)), + Metro: GetString(GetValueFromRequest(values, redirectQueryParams, models.GeoMetroORTBParam)), + ZIP: GetString(GetValueFromRequest(values, redirectQueryParams, models.GeoZipORTBParam)), + UTCOffset: GetCustomAtoI64(GetString(GetValueFromRequest(values, redirectQueryParams, models.GeoUTOffsetORTBParam))), + }, + } + + paid := GetCustomAtoI8(GetString(GetValueFromRequest(values, redirectQueryParams, models.AppPaidORTBParam))) + if paid != nil { + bidRequest.App.Paid = *paid + } + + js := GetCustomAtoI8(GetString(GetValueFromRequest(values, redirectQueryParams, models.DeviceJSORTBParam))) + if js != nil { + bidRequest.Device.JS = *js + } + + if locationTypeValue := GetCustomAtoI8(GetString(GetValueFromRequest(values, redirectQueryParams, models.GeoTypeORTBParam))); locationTypeValue != nil { + bidRequest.Device.Geo.Type = adcom1.LocationType(*locationTypeValue) + } + + var deviceExt models.ExtDevice + + if session_id := GetValueFromRequest(values, redirectQueryParams, models.DeviceExtSessionID); session_id != nil { + deviceExt.SessionID = GetString(session_id) + } + + if ifaType := GetValueFromRequest(values, redirectQueryParams, models.DeviceExtIfaType); ifaType != nil { + deviceExt.ExtDevice = &openrtb_ext.ExtDevice{ + IFAType: GetString(ifaType), + } + } + bidRequest.Device.Ext, _ = json.Marshal(deviceExt) + } else { + url := redirectQueryParams.Get(models.URLKey) + if url == "" { + url = redirectQueryParams.Get(models.DescriptionURLKey) + } + if url == "" { + url = payload.Request.Header.Get(models.PAGE_URL_HEADER) + } + + bidRequest.Site = &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: pubID, + }, + Content: content, + Page: url, + } + } + + bidderParams := GetString(GetValueFromRequest(values, redirectQueryParams, models.BidderParams)) + impPrebidExt := GetString(GetValueFromRequest(values, redirectQueryParams, models.ImpPrebidExt)) + updatedImpExt := false + impExt := "{" + if bidderParams != "" { + impExt += "bidder" + bidderParams + updatedImpExt = true + } + if impPrebidExt != "" { + if updatedImpExt { + impExt += "," + } + impExt += "prebid" + impPrebidExt + updatedImpExt = true + } + impExt += "}" + bidRequest.Imp[0].Ext = json.RawMessage(impExt) + + if validationFailed { + return models.RequestExtWrapper{}, errors.New("validation failed") + } + + if gdprFlag != "" || ccpa != "" { + bidRequest.Regs = &openrtb2.Regs{} + regsExt := openrtb_ext.ExtRegs{} + + if gdprFlag != "" { + gdprInt, _ := strconv.ParseInt(gdprFlag, 10, 8) + gdprInt8 := int8(gdprInt) + regsExt.GDPR = &gdprInt8 + } + + if ccpa != "" { + regsExt.USPrivacy = ccpa + } + + bidRequest.Regs.Ext, _ = json.Marshal(regsExt) + } + + bidRequest.User = &openrtb2.User{ + ID: GetString(GetValueFromRequest(values, redirectQueryParams, models.UserIDORTBParam)), + Gender: GetString(GetValueFromRequest(values, redirectQueryParams, models.UserGenderORTBParam)), + Yob: GetCustomAtoI64(GetString(GetValueFromRequest(values, redirectQueryParams, models.UserYobORTBParam))), + } + + if consentString != "" || eids != "" { + userExt := openrtb_ext.ExtUser{ + Consent: consentString, + } + + var eidList []openrtb2.EID + if err := json.Unmarshal([]byte(eids), &eidList); err == nil { + userExt.Eids = eidList + } + + bidRequest.User.Ext, _ = json.Marshal(userExt) + } + + sourceExt := models.ExtSource{} + if omidpv := GetString(GetValueFromRequest(values, redirectQueryParams, models.SourceOmidpvORTBParam)); omidpv != "" { + sourceExt.OMIDPV = omidpv + } + if omidpn := GetString(GetValueFromRequest(values, redirectQueryParams, models.SourceOmidpnORTBParam)); omidpn != "" { + sourceExt.OMIDPN = omidpn + } + bidRequest.Source = &openrtb2.Source{} + bidRequest.Source.Ext, _ = json.Marshal(sourceExt) + + profileId, _ := strconv.Atoi(profileID) + displayVersion := 0 + if val := getValueForKeyFromParams(models.VERSION_KEY, appReq, values, redirectQueryParams); val != "" { + displayVersion, _ = strconv.Atoi(val) + } + + contentTransparency := values.Get(models.ContentTransparency) + var transparency map[string]openrtb_ext.TransparencyRule + if contentTransparency != "" { + _ = json.Unmarshal([]byte(contentTransparency), &transparency) + } + + requestExtWrapper := models.RequestExtWrapper{ + ProfileId: profileId, + VersionId: displayVersion, + SumryDisableFlag: 1, + SSAuctionFlag: 1, + } + requestExt := models.RequestExt{ + Wrapper: &requestExtWrapper, + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Debug: getValueForKeyFromParams(models.DEBUG_KEY, appReq, values, redirectQueryParams) == "1", + Transparency: &openrtb_ext.TransparencyExt{ + Content: transparency, + }, + }, + }, + } + bidRequest.Ext, _ = json.Marshal(requestExt) + + // Replace macro values in DFP URL + for k := range values { + if strings.HasPrefix(k, models.MacroPrefix) { + paramName := strings.TrimPrefix(k, models.MacroPrefix) + redirectQueryParams.Set(paramName, values.Get(k)) + } + } + DFPControllerValue := rand.Int() + redirectQueryParams.Set(models.Correlator, strconv.Itoa(DFPControllerValue)) + redirectURL.RawQuery = redirectQueryParams.Encode() + owRedirectURLStr = redirectURL.String() + //Update Original HTTP Request with updated value of 'owredirect' + values.Set(models.OWRedirectURLKey, owRedirectURLStr) + rawQuery := values.Encode() + + body, err := json.Marshal(bidRequest) + if err != nil { + return requestExtWrapper, err + } + + result.ChangeSet.AddMutation(func(ep hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { + ep.Request.URL.RawQuery = rawQuery + // ep.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) + ep.Body = body + return ep, nil + }, hookstage.MutationUpdate, "entrypoint-update-amp-redirect-url") + + return requestExtWrapper, nil +} + +// GetValueFromRequest contains logic to get value for parameter identified by 'key' +func GetValueFromRequest(requestQueryParams, redirectQueryParams url.Values, key string) interface{} { + values := requestQueryParams + switch key { + case models.MimeORTBParam: + // DFP currently does not have a parameter defined for Mime types + if values.Get(models.OWMimeTypes) != "" { + mimeStrArr := strings.Split(values.Get(models.OWMimeTypes), models.MimesSeparator) + mimeValueArr := make([]string, 0) + for _, mime := range mimeStrArr { + if models.MimeIDToValueMap[mime] != "" { + mimeValueArr = append(mimeValueArr, models.MimeIDToValueMap[mime]) + } + } + return mimeValueArr + } + case models.OwAppKeywords: + if values.Get(models.OwAppKeywords) != "" { + return values.Get(models.OwAppKeywords) + } + case models.MaxDurationORTBParam: + return getValue(models.MaxDurationORTBParam, values, redirectQueryParams, models.DFPMaxAdDuration, models.OWMaxAdDuration) + case models.MinDurationORTBParam: + return getValue(models.MinDurationORTBParam, values, redirectQueryParams, models.DFPMinAdDuration, models.OWMinAdDuration) + case models.StartDelayORTBParam: + if values.Get(models.OWStartDelay) != "" { + return values.Get(models.OWStartDelay) + } else if redirectQueryParams.Get(models.DFPVPos) != "" { + posStr := redirectQueryParams.Get(models.DFPVPos) + return models.VideoPositionToStartDelayMap[posStr] + } + case models.PlaybackMethodORTBParam: + var pbStr string + if values.Get(models.OWPlaybackMethod) != "" { + pbStr = values.Get(models.OWPlaybackMethod) + } else if redirectQueryParams.Get(models.DFPVpmute) != "" || redirectQueryParams.Get(models.DFPVpa) != "" { + if redirectQueryParams.Get(models.DFPVpmute) == "1" && redirectQueryParams.Get(models.DFPVpa) == "0" { + pbStr = "2,6" + } else if redirectQueryParams.Get(models.DFPVpmute) == "0" && redirectQueryParams.Get(models.DFPVpa) == "1" { + pbStr = "1,2" + } else if redirectQueryParams.Get(models.DFPVpmute) == "1" && redirectQueryParams.Get(models.DFPVpa) == "1" { + pbStr = "2" + } + } + if pbStr != "" { + pbIntArr := make([]int, 0) + for _, pb := range strings.Split(pbStr, ",") { + pbInt, _ := strconv.Atoi(pb) + pbIntArr = append(pbIntArr, pbInt) + } + return pbIntArr + } + case models.APIORTBParam: + return getValueInArray(models.APIORTBParam, values, redirectQueryParams, "", models.OWAPI) + case models.DeliveryORTBParam: + return getValueInArray(models.DeliveryORTBParam, values, redirectQueryParams, "", models.OWDelivery) + case models.ProtocolsORTBParam: + return getValueInArray(models.ProtocolsORTBParam, values, redirectQueryParams, "", models.OWProtocols) + case models.BAttrORTBParam: + return getValueInArray(models.BAttrORTBParam, values, redirectQueryParams, "", models.OWBAttr) + case models.LinearityORTBParam: + if values.Get(models.OWLinearity) != "" { + return values.Get(models.OWLinearity) + } else if redirectQueryParams.Get(models.DFPVAdType) != "" { + adtypeStr := redirectQueryParams.Get(models.DFPVAdType) + return models.LinearityMap[adtypeStr] + } + case models.PlacementORTBParam: + return getValue(models.PlacementORTBParam, values, redirectQueryParams, "", models.OWPlacement) + case models.MinBitrateORTBParam: + return getValue(models.MinBitrateORTBParam, values, redirectQueryParams, "", models.OWMinBitrate) + case models.MaxBitrateORTBParam: + return getValue(models.MaxBitrateORTBParam, values, redirectQueryParams, "", models.OWMaxBitrate) + case models.SkipORTBParam: + return getValue(models.SkipORTBParam, values, redirectQueryParams, "", models.OWSkippable) + case models.SkipMinORTBParam: + return getValue(models.SkipMinORTBParam, values, redirectQueryParams, "", models.OWSkipMin) + case models.SkipAfterORTBParam: + return getValue(models.SkipAfterORTBParam, values, redirectQueryParams, "", models.OWSkipAfter) + case models.SequenceORTBParam: + return getValue(models.SequenceORTBParam, values, redirectQueryParams, "", models.OWSequence) + case models.BoxingAllowedORTBParam: + return getValue(models.BoxingAllowedORTBParam, values, redirectQueryParams, "", models.OWBoxingAllowed) + case models.MaxExtendedORTBParam: + return getValue(models.MaxExtendedORTBParam, values, redirectQueryParams, "", models.OWMaxExtended) + case models.ProtocolORTBParam: + return getValue(models.ProtocolORTBParam, values, redirectQueryParams, "", models.OWProtocol) + case models.PosORTBParam: + return getValue(models.PosORTBParam, values, redirectQueryParams, "", models.OWPos) + case models.AppIDORTBParam: + return getValue(models.AppIDORTBParam, values, redirectQueryParams, "", models.OWAppId) + case models.AppNameORTBParam: + return getValue(models.AppNameORTBParam, values, redirectQueryParams, "", models.OWAppName) + case models.AppBundleORTBParam: + return getValue(models.AppBundleORTBParam, values, redirectQueryParams, "", models.OWAppBundle) + case models.AppDomainORTBParam: + return getValue(models.AppDomainORTBParam, values, redirectQueryParams, "", models.OWAppDomain) + case models.AppStoreURLORTBParam: + return getValue(models.AppStoreURLORTBParam, values, redirectQueryParams, "", models.OWAppStoreURL) + case models.AppCatORTBParam: + if values.Get(models.OWAppCat) != "" { + catStrArr := strings.Split(values.Get(models.OWAppCat), models.Comma) + return catStrArr + } + case models.AppPaidORTBParam: + return getValue(models.AppPaidORTBParam, values, redirectQueryParams, "", models.OWAppPaid) + case models.DeviceUAORTBParam: + return getValue(models.DeviceUAORTBParam, values, redirectQueryParams, "", models.OWDeviceUA) + case models.DeviceIPORTBParam: + return getValue(models.DeviceIPORTBParam, values, redirectQueryParams, "", models.OWDeviceIP) + case models.DeviceLMTORTBParam: + return getValue(models.DeviceLMTORTBParam, values, redirectQueryParams, "", models.OWDeviceLMT) + case models.DeviceDNTORTBParam: + return getValue(models.DeviceDNTORTBParam, values, redirectQueryParams, "", models.OWDeviceDNT) + case models.DeviceJSORTBParam: + return getValue(models.DeviceJSORTBParam, values, redirectQueryParams, "", models.OWDeviceJS) + case models.GeoLatORTBParam: + return getValue(models.GeoLatORTBParam, values, redirectQueryParams, "", models.OWGeoLat) + case models.GeoLonORTBParam: + return getValue(models.GeoLonORTBParam, values, redirectQueryParams, "", models.OWGeoLon) + case models.GeoTypeORTBParam: + return getValue(models.GeoTypeORTBParam, values, redirectQueryParams, "", models.OWGeoType) + case models.GeoCountryORTBParam: + return getValue(models.GeoCountryORTBParam, values, redirectQueryParams, "", models.OWGeoCountry) + case models.GeoCityORTBParam: + return getValue(models.GeoCityORTBParam, values, redirectQueryParams, "", models.OWGeoCity) + case models.GeoMetroORTBParam: + return getValue(models.GeoMetroORTBParam, values, redirectQueryParams, "", models.OWGeoMetro) + case models.GeoZipORTBParam: + return getValue(models.GeoZipORTBParam, values, redirectQueryParams, "", models.OWGeoZip) + case models.GeoUTOffsetORTBParam: + return getValue(models.GeoUTOffsetORTBParam, values, redirectQueryParams, "", models.OWUTOffset) + case models.DeviceIfaORTBParam: + return getValue(models.DeviceIfaORTBParam, values, redirectQueryParams, "", models.OWDeviceIfa) + case models.DeviceDidsha1ORTBParam: + return getValue(models.DeviceDidsha1ORTBParam, values, redirectQueryParams, "", models.OWDeviceDidsha1) + case models.DeviceDidmd5ORTBParam: + return getValue(models.DeviceDidmd5ORTBParam, values, redirectQueryParams, "", models.OWDeviceDidmd5) + case models.DeviceDpidsha1ORTBParam: + return getValue(models.DeviceDpidsha1ORTBParam, values, redirectQueryParams, "", models.OWDeviceDpidsha1) + case models.DeviceDpidmd5ORTBParam: + return getValue(models.DeviceDpidmd5ORTBParam, values, redirectQueryParams, "", models.OWDeviceDpidmd5) + case models.DeviceMacsha1ORTBParam: + return getValue(models.DeviceMacsha1ORTBParam, values, redirectQueryParams, "", models.OWDeviceMacsha1) + case models.DeviceMacmd5ORTBParam: + return getValue(models.DeviceMacmd5ORTBParam, values, redirectQueryParams, "", models.OWDeviceMacmd5) + case models.UserIDORTBParam: + return getValue(models.UserIDORTBParam, values, redirectQueryParams, "", models.OWUserID) + case models.SizeORTBParam: + if values.Get(models.OWSize) != "" { + return values.Get(models.OWSize) + } else if redirectQueryParams.Get(models.DFPSize) != "" { + // If multiple sizes are passed in DFP parameter, we will consider only the first + DFPSizeStr := strings.Split(redirectQueryParams.Get(models.DFPSize), models.MultipleSizeSeparator) + return DFPSizeStr[0] + } + case models.ContentGenreORTBParam: + return getValue(models.ContentGenreORTBParam, values, redirectQueryParams, "", models.OWContentGenre) + case models.ContentTitleORTBParam: + return getValue(models.ContentTitleORTBParam, values, redirectQueryParams, "", models.OWContentTitle) + case models.UserGenderORTBParam: + return getValue(models.UserGenderORTBParam, values, redirectQueryParams, "", models.OWUserGender) + case models.UserYobORTBParam: + return getValue(models.UserYobORTBParam, values, redirectQueryParams, "", models.OWUserYob) + case models.SourceOmidpvORTBParam: + return getValue(models.SourceOmidpvORTBParam, values, redirectQueryParams, "", models.OWSourceOmidPv) + case models.SourceOmidpnORTBParam: + return getValue(models.SourceOmidpnORTBParam, values, redirectQueryParams, "", models.OWSourceOmidPn) + case models.BidderParams: + return getValue(models.BidderParams, values, redirectQueryParams, "", models.OWBidderParams) + case models.DeviceExtSessionID: + if _, ok := values[models.OWDeviceExtSessionID]; ok { + return values.Get(models.OWDeviceExtSessionID) + } + case models.DeviceExtIfaType: + if _, ok := values[models.OWDeviceExtIfaType]; ok { + return values.Get(models.OWDeviceExtIfaType) + } + case models.FloorValue: + if _, ok := values[models.FloorValue]; ok { + return values.Get(models.FloorValue) + } + case models.FloorCurrency: + if _, ok := values[models.FloorCurrency]; ok { + return values.Get(models.FloorCurrency) + } + case models.ImpPrebidExt: + return getValue(models.ImpPrebidExt, values, redirectQueryParams, "", models.OWImpPrebidExt) + } + return nil + +} + +func getValue(oRTBParamName string, values url.Values, redirectQueryParams url.Values, DFPParamName string, OWParamName string) interface{} { + paramArr := models.ORTBToDFPOWMap[oRTBParamName] + if paramArr == nil { + return nil + } + + if values.Get(OWParamName) != "" { + return values.Get(OWParamName) + } else if paramArr[1] != "" && DFPParamName != "" && redirectQueryParams.Get(DFPParamName) != "" { + return redirectQueryParams.Get(DFPParamName) + } + + return nil +} + +func getValueInArray(oRTBParamName string, values url.Values, redirectQueryParams url.Values, DFPParamName string, OWParamName string) interface{} { + valStr := GetString(getValue(oRTBParamName, values, redirectQueryParams, DFPParamName, OWParamName)) + if valStr != "" { + valIntArr := make([]int, 0) + for _, val := range strings.Split(valStr, ",") { + valInt, _ := strconv.Atoi(val) + valIntArr = append(valIntArr, valInt) + } + return valIntArr + } + return nil +} + +func GetString(val interface{}) string { + var result string + if val != nil { + result, ok := val.(string) + if ok { + return result + } + } + return result +} + +func GetStringArr(val interface{}) []string { + var result []string + if val != nil { + result, ok := val.([]string) + if ok { + return result + } + } + return result +} + +func GetIntArr(val interface{}) []int { + var result []int + if val != nil { + result, ok := val.([]int) + if ok { + return result + } + } + return result +} + +func GetInt(val interface{}) int { + var result int + if val != nil { + result, ok := val.(int) + if ok { + return result + } + } + return result +} + +func GetCustomAtoI8(s string) *int8 { + if s == "" { + return nil + } + i, ok := strconv.Atoi(s) + if ok == nil { + i8 := int8(i) + return &i8 + } + return nil +} + +func GetCustomAtoI64(s string) int64 { + if s == "" { + return 0 + } + i, ok := strconv.ParseInt(s, 10, 64) + if ok == nil { + return i + } + return 0 +} + +func GetCustomStrToFloat(s string) float64 { + if s == "" { + return 0 + } + f, ok := strconv.ParseFloat(s, 64) + if ok == nil { + return f + } + return 0 +} + +// getValueForKeyFromParams returns value for a key from the request params, if not present in request params +// then it checks url/description_url in the redirect URL +func getValueForKeyFromParams(key string, appReq string, requestParams, redirectURLParams url.Values) string { + var value string + + //first check from request query params + if val := requestParams.Get(key); val != "" { + return val + } + + //else check it in url/description_url in redirect url query params + urlStr := getURLfromRedirectURL(redirectURLParams, appReq) + + if urlStr != "" { + if urlObj, urlErr := url.Parse(urlStr); urlErr == nil { + URLQueryParams := urlObj.Query() + if val := URLQueryParams.Get(key); val != "" { + return val + } + } + } + return value + +} + +// getURLfromRedirectURL return 'url' from redirectURL and if url is not present it returns desc URL for web request +func getURLfromRedirectURL(redirectQueryParams url.Values, appReq string) string { + var URL string + + //check for 'url' query param + if urlStr := redirectQueryParams.Get(models.URLKey); urlStr != "" { + return urlStr + } + + //if 'url' is not present, check for 'description_url' key + if appReq != "1" { + if descURL := redirectQueryParams.Get(models.DescriptionURLKey); descURL != "" { + return descURL + } + } + return URL +} diff --git a/modules/pubmatic/openwrap/entrypointhook.go b/modules/pubmatic/openwrap/entrypointhook.go new file mode 100644 index 00000000000..45a9d879c5a --- /dev/null +++ b/modules/pubmatic/openwrap/entrypointhook.go @@ -0,0 +1,171 @@ +package openwrap + +import ( + "context" + "strconv" + "time" + + "github.com/buger/jsonparser" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + v25 "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/usersync" + uuid "github.com/satori/go.uuid" +) + +const ( + OpenWrapAuction = "/pbs/openrtb2/auction" + OpenWrapV25 = "/openrtb/2.5" + OpenWrapV25Video = "/openrtb/2.5/video" + OpenWrapOpenRTBVideo = "/video/openrtb" + OpenWrapVAST = "/video/vast" + OpenWrapJSON = "/video/json" + OpenWrapAmp = "/amp" +) + +func (m OpenWrap) handleEntrypointHook( + _ context.Context, + miCtx hookstage.ModuleInvocationContext, + payload hookstage.EntrypointPayload, +) (result hookstage.HookResult[hookstage.EntrypointPayload], err error) { + queryParams := payload.Request.URL.Query() + source := queryParams.Get("source") //source query param to identify /openrtb2/auction type + + rCtx := models.RequestCtx{} + var endpoint string + var pubid int + var requestExtWrapper models.RequestExtWrapper + defer func() { + if result.Reject { + m.metricEngine.RecordBadRequests(endpoint, getPubmaticErrorCode(result.NbrCode)) + } else { + result.ModuleContext = make(hookstage.ModuleContext) + result.ModuleContext["rctx"] = rCtx + } + }() + + rCtx.Sshb = queryParams.Get("sshb") + //Do not execute the module for requests processed in SSHB(8001) + 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 "inapp": + 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 + 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 + + if err != nil { + result.NbrCode = nbr.InvalidRequestWrapperExtension + result.Errors = append(result.Errors, "InvalidRequest") + return result, err + } + + if requestExtWrapper.ProfileId <= 0 { + result.NbrCode = nbr.InvalidProfileID + result.Errors = append(result.Errors, "ErrMissingProfileID") + return result, err + } + + requestDebug, _ := jsonparser.GetBoolean(payload.Body, "ext", "prebid", "debug") + rCtx = models.RequestCtx{ + StartTime: time.Now().Unix(), + Debug: queryParams.Get(models.Debug) == "1" || requestDebug, + UA: payload.Request.Header.Get("User-Agent"), + ProfileID: requestExtWrapper.ProfileId, + DisplayID: requestExtWrapper.VersionId, + DisplayVersionID: requestExtWrapper.VersionId, + LogInfoFlag: requestExtWrapper.LogInfoFlag, + SupportDeals: requestExtWrapper.SupportDeals, + ABTestConfig: requestExtWrapper.ABTestConfig, + SSAuction: requestExtWrapper.SSAuctionFlag, + SummaryDisable: requestExtWrapper.SumryDisableFlag, + LoggerImpressionID: requestExtWrapper.LoggerImpressionID, + ClientConfigFlag: requestExtWrapper.ClientConfigFlag, + SSAI: requestExtWrapper.SSAI, + IP: models.GetIP(payload.Request), + IsCTVRequest: models.IsCTVAPIRequest(payload.Request.URL.Path), + TrackerEndpoint: m.cfg.Tracker.Endpoint, + VideoErrorTrackerEndpoint: m.cfg.Tracker.VideoErrorTrackerEndpoint, + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: strconv.Itoa(requestExtWrapper.ProfileId), + Endpoint: endpoint, + MetricsEngine: m.metricEngine, + DCName: m.cfg.Server.DCName, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + ParsedUidCookie: usersync.ReadCookie(payload.Request, usersync.Base64Decoder{}, &config.HostCookie{}), + TMax: m.cfg.Timeout.MaxTimeout, + CurrencyConversion: func(from, to string, value float64) (float64, error) { + rate, err := m.currencyConversion.GetRate(from, to) + if err == nil { + return value * rate, nil + } + return 0, err + }, + } + + // only http.ErrNoCookie is returned, we can ignore it + rCtx.UidCookie, _ = payload.Request.Cookie(models.UidCookieName) + rCtx.KADUSERCookie, _ = payload.Request.Cookie(models.KADUSERCOOKIE) + if originCookie, _ := payload.Request.Cookie("origin"); originCookie != nil { + rCtx.OriginCookie = originCookie.Value + } + + if rCtx.LoggerImpressionID == "" { + rCtx.LoggerImpressionID = uuid.NewV4().String() + } + + // temp, for AMP, etc + if pubid != 0 { + rCtx.PubID = pubid + } + + result.Reject = false + return result, nil +} diff --git a/modules/pubmatic/openwrap/entrypointhook_test.go b/modules/pubmatic/openwrap/entrypointhook_test.go new file mode 100644 index 00000000000..c2e76a4289e --- /dev/null +++ b/modules/pubmatic/openwrap/entrypointhook_test.go @@ -0,0 +1,531 @@ +package openwrap + +import ( + "context" + "net/http" + "testing" + + "github.com/golang/mock/gomock" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestOpenWrap_handleEntrypointHook(t *testing.T) { + ctrl := gomock.NewController(t) + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + defer ctrl.Finish() + + type fields struct { + cfg config.Config + cache cache.Cache + } + type args struct { + in0 context.Context + miCtx hookstage.ModuleInvocationContext + payload hookstage.EntrypointPayload + setup func(*mock_metrics.MockMetricsEngine) + } + tests := []struct { + name string + fields fields + args args + want hookstage.HookResult[hookstage.EntrypointPayload] + wantErr error + }{ + { + name: "request with sshb=1 should not execute entrypointhook", + args: args{ + in0: context.Background(), + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb/2.5?sshb=1", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("SOURCE_IP", "127.0.0.1") + r.Header.Add("Cookie", `KADUSERCOOKIE=7D75D25F-FAC9-443D-B2D1-B17FEE11E027; DPSync3=1684886400%3A248%7C1685491200%3A245_226_201; KRTBCOOKIE_80=16514-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&22987-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23025-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23386-CAESEMih0bN7ISRdZT8xX8LXzEw; KRTBCOOKIE_377=6810-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&22918-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&23031-59dc50c9-d658-44ce-b442-5a1f344d97c0; uids=eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=; KRTBCOOKIE_153=1923-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&19420-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&22979-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&23462-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse; KRTBCOOKIE_57=22776-41928985301451193&KRTB&23339-41928985301451193; KRTBCOOKIE_27=16735-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&16736-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23019-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23114-uid:3cab6283-4546-4500-a7b6-40ef605fe745; KRTBCOOKIE_18=22947-1978557989514665832; KRTBCOOKIE_466=16530-4fc36250-d852-459c-8772-7356de17ab97; KRTBCOOKIE_391=22924-8044608333778839078&KRTB&23263-8044608333778839078&KRTB&23481-8044608333778839078; KRTBCOOKIE_1310=23431-b81c3g7dr67i&KRTB&23446-b81c3g7dr67i&KRTB&23465-b81c3g7dr67i; KRTBCOOKIE_1290=23368-vkf3yv9lbbl; KRTBCOOKIE_22=14911-4554572065121110164&KRTB&23150-4554572065121110164; KRTBCOOKIE_860=16335-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23334-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23417-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23426-YGAqDU1zUTdjyAFxCoe3kctlNPo; KRTBCOOKIE_904=16787-KwJwE7NkCZClNJRysN2iYg; KRTBCOOKIE_1159=23138-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23328-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23427-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23445-5545f53f3d6e4ec199d8ed627ff026f3; KRTBCOOKIE_32=11175-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22713-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22715-AQEI_1QecY2ESAIjEW6KAQEBAQE; SyncRTB3=1685577600%3A35%7C1685491200%3A107_21_71_56_204_247_165_231_233_179_22_209_54_254_238_96_99_220_7_214_13_3_8_234_176_46_5%7C1684886400%3A2_223_15%7C1689465600%3A69%7C1685145600%3A63; KRTBCOOKIE_107=1471-uid:EK38R0PM1NQR0H5&KRTB&23421-uid:EK38R0PM1NQR0H5; KRTBCOOKIE_594=17105-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004&KRTB&17107-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004; SPugT=1684310122; chkChromeAb67Sec=133; KRTBCOOKIE_699=22727-AAFy2k7FBosAAEasbJoXnw; PugT=1684310473; origin=go-test`) + return r + }(), + Body: []byte(`{"ext":{"wrapper":{"profileid":5890,"versionid":1}}}`), + }, + setup: func(mme *mock_metrics.MockMetricsEngine) {}, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + Sshb: "1", + }, + }, + }, + }, + { + name: "valid /openrtb2/2.5 request(reverseProxy through SSHB(8001->8000))", + fields: fields{ + cfg: config.Config{ + Tracker: config.Tracker{ + Endpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + }, + }, + cache: nil, + }, + args: args{ + in0: context.Background(), + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb/2.5?debug=1&sshb=2", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("SOURCE_IP", "127.0.0.1") + r.Header.Add("Cookie", `KADUSERCOOKIE=7D75D25F-FAC9-443D-B2D1-B17FEE11E027; DPSync3=1684886400%3A248%7C1685491200%3A245_226_201; KRTBCOOKIE_80=16514-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&22987-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23025-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23386-CAESEMih0bN7ISRdZT8xX8LXzEw; KRTBCOOKIE_377=6810-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&22918-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&23031-59dc50c9-d658-44ce-b442-5a1f344d97c0; uids=eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=; KRTBCOOKIE_153=1923-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&19420-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&22979-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&23462-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse; KRTBCOOKIE_57=22776-41928985301451193&KRTB&23339-41928985301451193; KRTBCOOKIE_27=16735-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&16736-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23019-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23114-uid:3cab6283-4546-4500-a7b6-40ef605fe745; KRTBCOOKIE_18=22947-1978557989514665832; KRTBCOOKIE_466=16530-4fc36250-d852-459c-8772-7356de17ab97; KRTBCOOKIE_391=22924-8044608333778839078&KRTB&23263-8044608333778839078&KRTB&23481-8044608333778839078; KRTBCOOKIE_1310=23431-b81c3g7dr67i&KRTB&23446-b81c3g7dr67i&KRTB&23465-b81c3g7dr67i; KRTBCOOKIE_1290=23368-vkf3yv9lbbl; KRTBCOOKIE_22=14911-4554572065121110164&KRTB&23150-4554572065121110164; KRTBCOOKIE_860=16335-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23334-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23417-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23426-YGAqDU1zUTdjyAFxCoe3kctlNPo; KRTBCOOKIE_904=16787-KwJwE7NkCZClNJRysN2iYg; KRTBCOOKIE_1159=23138-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23328-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23427-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23445-5545f53f3d6e4ec199d8ed627ff026f3; KRTBCOOKIE_32=11175-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22713-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22715-AQEI_1QecY2ESAIjEW6KAQEBAQE; SyncRTB3=1685577600%3A35%7C1685491200%3A107_21_71_56_204_247_165_231_233_179_22_209_54_254_238_96_99_220_7_214_13_3_8_234_176_46_5%7C1684886400%3A2_223_15%7C1689465600%3A69%7C1685145600%3A63; KRTBCOOKIE_107=1471-uid:EK38R0PM1NQR0H5&KRTB&23421-uid:EK38R0PM1NQR0H5; KRTBCOOKIE_594=17105-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004&KRTB&17107-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004; SPugT=1684310122; chkChromeAb67Sec=133; KRTBCOOKIE_699=22727-AAFy2k7FBosAAEasbJoXnw; PugT=1684310473; origin=go-test`) + return r + }(), + Body: []byte(`{"ext":{"wrapper":{"profileid":5890,"versionid":1}}}`), + }, + setup: func(mme *mock_metrics.MockMetricsEngine) {}, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + ProfileID: 5890, + DisplayID: 1, + DisplayVersionID: 1, + SSAuction: -1, + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: false, + TrackerEndpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + UidCookie: &http.Cookie{ + Name: "uids", + Value: `eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=`, + }, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: `7D75D25F-FAC9-443D-B2D1-B17FEE11E027`, + }, + OriginCookie: "go-test", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: "5890", + Endpoint: models.EndpointV25, + MetricsEngine: mockEngine, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + }, + }, + }, + wantErr: nil, + }, + { + name: "valid /openrtb/2.5 request with wiid set and no cookies(reverseProxy through SSHB(8001->8000)", + fields: fields{ + cfg: config.Config{ + Tracker: config.Tracker{ + Endpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + }, + }, + cache: nil, + }, + args: args{ + in0: context.Background(), + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb/2.5?debug=1&sshb=2", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("SOURCE_IP", "127.0.0.1") + return r + }(), + Body: []byte(`{"ext":{"wrapper":{"profileid":5890,"versionid":1,"wiid":"4df09505-d0b2-4d70-94d9-dc41e8e777f7"}}}`), + }, + setup: func(mme *mock_metrics.MockMetricsEngine) {}, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + ProfileID: 5890, + DisplayID: 1, + DisplayVersionID: 1, + SSAuction: -1, + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: false, + TrackerEndpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + LoggerImpressionID: "4df09505-d0b2-4d70-94d9-dc41e8e777f7", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: "5890", + Endpoint: models.EndpointV25, + MetricsEngine: mockEngine, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + }, + }, + }, + wantErr: nil, + }, + { + name: "/openrtb/2.5 request without profileid(reverseProxy through SSHB(8001->8000)", + fields: fields{ + cfg: config.Config{}, + cache: nil, + }, + args: args{ + in0: context.Background(), + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb/2.5?&sshb=2", nil) + if err != nil { + panic(err) + } + return r + }(), + Body: []byte(`{"ext":{"wrapper":{"profileids":5890,"versionid":1}}}`), + }, + setup: func(mme *mock_metrics.MockMetricsEngine) { + mme.EXPECT().RecordBadRequests(gomock.Any(), 700) + }, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + Reject: true, + NbrCode: nbr.InvalidProfileID, + Errors: []string{"ErrMissingProfileID"}, + }, + wantErr: nil, + }, + { + name: "Valid /openrtb2/auction?source=pbjs request(ows2s)", + fields: fields{ + cfg: config.Config{}, + cache: nil, + }, + args: args{ + in0: context.Background(), + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb2/auction?source=pbjs&debug=1", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("SOURCE_IP", "127.0.0.1") + r.Header.Add("Cookie", `KADUSERCOOKIE=7D75D25F-FAC9-443D-B2D1-B17FEE11E027; DPSync3=1684886400%3A248%7C1685491200%3A245_226_201; KRTBCOOKIE_80=16514-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&22987-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23025-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23386-CAESEMih0bN7ISRdZT8xX8LXzEw; KRTBCOOKIE_377=6810-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&22918-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&23031-59dc50c9-d658-44ce-b442-5a1f344d97c0; uids=eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=; KRTBCOOKIE_153=1923-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&19420-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&22979-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&23462-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse; KRTBCOOKIE_57=22776-41928985301451193&KRTB&23339-41928985301451193; KRTBCOOKIE_27=16735-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&16736-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23019-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23114-uid:3cab6283-4546-4500-a7b6-40ef605fe745; KRTBCOOKIE_18=22947-1978557989514665832; KRTBCOOKIE_466=16530-4fc36250-d852-459c-8772-7356de17ab97; KRTBCOOKIE_391=22924-8044608333778839078&KRTB&23263-8044608333778839078&KRTB&23481-8044608333778839078; KRTBCOOKIE_1310=23431-b81c3g7dr67i&KRTB&23446-b81c3g7dr67i&KRTB&23465-b81c3g7dr67i; KRTBCOOKIE_1290=23368-vkf3yv9lbbl; KRTBCOOKIE_22=14911-4554572065121110164&KRTB&23150-4554572065121110164; KRTBCOOKIE_860=16335-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23334-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23417-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23426-YGAqDU1zUTdjyAFxCoe3kctlNPo; KRTBCOOKIE_904=16787-KwJwE7NkCZClNJRysN2iYg; KRTBCOOKIE_1159=23138-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23328-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23427-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23445-5545f53f3d6e4ec199d8ed627ff026f3; KRTBCOOKIE_32=11175-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22713-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22715-AQEI_1QecY2ESAIjEW6KAQEBAQE; SyncRTB3=1685577600%3A35%7C1685491200%3A107_21_71_56_204_247_165_231_233_179_22_209_54_254_238_96_99_220_7_214_13_3_8_234_176_46_5%7C1684886400%3A2_223_15%7C1689465600%3A69%7C1685145600%3A63; KRTBCOOKIE_107=1471-uid:EK38R0PM1NQR0H5&KRTB&23421-uid:EK38R0PM1NQR0H5; KRTBCOOKIE_594=17105-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004&KRTB&17107-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004; SPugT=1684310122; chkChromeAb67Sec=133; KRTBCOOKIE_699=22727-AAFy2k7FBosAAEasbJoXnw; PugT=1684310473; origin=go-test`) + return r + }(), + Body: []byte(`{"imp":[{"tagid":"/43743431/DMDemo","ext":{"prebid":{"bidder":{"pubmatic":{"publisherId":"5890"}},"adunitcode":"div-gpt-ad-1460505748561-0"}},"id":"div-gpt-ad-1460505748561-0","banner":{"topframe":1,"format":[{"w":300,"h":250}]}}],"site":{"domain":"localhost:9999","publisher":{"domain":"localhost:9999","id":"5890"},"page":"http://localhost:9999/integrationExamples/gpt/owServer_example.html"},"device":{"w":1792,"h":446,"dnt":0,"ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36","language":"en","sua":{"source":1,"platform":{"brand":"macOS"},"browsers":[{"brand":"Google Chrome","version":["117"]},{"brand":"Not;A=Brand","version":["8"]},{"brand":"Chromium","version":["117"]}],"mobile":0}},"ext":{"prebid":{"auctiontimestamp":1697191822565,"targeting":{"includewinners":true,"includebidderkeys":true},"bidderparams":{"pubmatic":{"publisherId":"5890","wrapper":{"profileid":43563,"versionid":1}}},"channel":{"name":"pbjs","version":"v8.7.0-pre"},"createtids":false}},"id":"5bdd7da5-1166-40fe-a9cb-3bf3c3164cd3","test":0,"tmax":3000}`), + }, + setup: func(mme *mock_metrics.MockMetricsEngine) {}, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + ProfileID: 43563, + PubID: 0, + PubIDStr: "", + DisplayID: 1, + DisplayVersionID: 1, + SSAuction: -1, + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: false, + UidCookie: &http.Cookie{ + Name: "uids", + Value: `eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=`, + }, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: `7D75D25F-FAC9-443D-B2D1-B17FEE11E027`, + }, + OriginCookie: "go-test", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: "43563", + Endpoint: models.EndpointWebS2S, + MetricsEngine: mockEngine, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + }, + }, + }, + wantErr: nil, + }, + { + name: "Valid /openrtb2/auction?source=pbjs request(ows2s) debug set from request body instead of queryparam", + fields: fields{ + cfg: config.Config{}, + cache: nil, + }, + args: args{ + in0: context.Background(), + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb2/auction?source=pbjs", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("SOURCE_IP", "127.0.0.1") + r.Header.Add("Cookie", `KADUSERCOOKIE=7D75D25F-FAC9-443D-B2D1-B17FEE11E027; DPSync3=1684886400%3A248%7C1685491200%3A245_226_201; KRTBCOOKIE_80=16514-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&22987-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23025-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23386-CAESEMih0bN7ISRdZT8xX8LXzEw; KRTBCOOKIE_377=6810-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&22918-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&23031-59dc50c9-d658-44ce-b442-5a1f344d97c0; uids=eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=; KRTBCOOKIE_153=1923-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&19420-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&22979-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&23462-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse; KRTBCOOKIE_57=22776-41928985301451193&KRTB&23339-41928985301451193; KRTBCOOKIE_27=16735-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&16736-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23019-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23114-uid:3cab6283-4546-4500-a7b6-40ef605fe745; KRTBCOOKIE_18=22947-1978557989514665832; KRTBCOOKIE_466=16530-4fc36250-d852-459c-8772-7356de17ab97; KRTBCOOKIE_391=22924-8044608333778839078&KRTB&23263-8044608333778839078&KRTB&23481-8044608333778839078; KRTBCOOKIE_1310=23431-b81c3g7dr67i&KRTB&23446-b81c3g7dr67i&KRTB&23465-b81c3g7dr67i; KRTBCOOKIE_1290=23368-vkf3yv9lbbl; KRTBCOOKIE_22=14911-4554572065121110164&KRTB&23150-4554572065121110164; KRTBCOOKIE_860=16335-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23334-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23417-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23426-YGAqDU1zUTdjyAFxCoe3kctlNPo; KRTBCOOKIE_904=16787-KwJwE7NkCZClNJRysN2iYg; KRTBCOOKIE_1159=23138-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23328-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23427-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23445-5545f53f3d6e4ec199d8ed627ff026f3; KRTBCOOKIE_32=11175-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22713-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22715-AQEI_1QecY2ESAIjEW6KAQEBAQE; SyncRTB3=1685577600%3A35%7C1685491200%3A107_21_71_56_204_247_165_231_233_179_22_209_54_254_238_96_99_220_7_214_13_3_8_234_176_46_5%7C1684886400%3A2_223_15%7C1689465600%3A69%7C1685145600%3A63; KRTBCOOKIE_107=1471-uid:EK38R0PM1NQR0H5&KRTB&23421-uid:EK38R0PM1NQR0H5; KRTBCOOKIE_594=17105-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004&KRTB&17107-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004; SPugT=1684310122; chkChromeAb67Sec=133; KRTBCOOKIE_699=22727-AAFy2k7FBosAAEasbJoXnw; PugT=1684310473; origin=go-test`) + return r + }(), + Body: []byte(`{"imp":[{"tagid":"/43743431/DMDemo","ext":{"prebid":{"bidder":{"pubmatic":{"publisherId":"5890"}},"adunitcode":"div-gpt-ad-1460505748561-0"}},"id":"div-gpt-ad-1460505748561-0","banner":{"topframe":1,"format":[{"w":300,"h":250}]}}],"site":{"domain":"localhost:9999","publisher":{"domain":"localhost:9999","id":"5890"},"page":"http://localhost:9999/integrationExamples/gpt/owServer_example.html"},"device":{"w":1792,"h":446,"dnt":0,"ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36","language":"en","sua":{"source":1,"platform":{"brand":"macOS"},"browsers":[{"brand":"Google Chrome","version":["117"]},{"brand":"Not;A=Brand","version":["8"]},{"brand":"Chromium","version":["117"]}],"mobile":0}},"ext":{"prebid":{"auctiontimestamp":1697191822565,"targeting":{"includewinners":true,"includebidderkeys":true},"bidderparams":{"pubmatic":{"publisherId":"5890","wrapper":{"profileid":43563,"versionid":1}}},"channel":{"name":"pbjs","version":"v8.7.0-pre"},"createtids":false,"debug":true}},"id":"5bdd7da5-1166-40fe-a9cb-3bf3c3164cd3","test":0,"tmax":3000}`), + }, + setup: func(mme *mock_metrics.MockMetricsEngine) {}, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + ProfileID: 43563, + PubID: 0, + PubIDStr: "", + DisplayID: 1, + DisplayVersionID: 1, + SSAuction: -1, + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: false, + UidCookie: &http.Cookie{ + Name: "uids", + Value: `eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=`, + }, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: `7D75D25F-FAC9-443D-B2D1-B17FEE11E027`, + }, + OriginCookie: "go-test", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: "43563", + Endpoint: models.EndpointWebS2S, + MetricsEngine: mockEngine, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + }, + }, + }, + wantErr: nil, + }, + { + name: "/openrtb2/auction?source=pbjs request(ows2s) without profileid", + fields: fields{ + cfg: config.Config{}, + cache: nil, + }, + args: args{ + in0: context.Background(), + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb2/auction?source=pbjs&debug=1", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("SOURCE_IP", "127.0.0.1") + r.Header.Add("Cookie", `KADUSERCOOKIE=7D75D25F-FAC9-443D-B2D1-B17FEE11E027; DPSync3=1684886400%3A248%7C1685491200%3A245_226_201; KRTBCOOKIE_80=16514-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&22987-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23025-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23386-CAESEMih0bN7ISRdZT8xX8LXzEw; KRTBCOOKIE_377=6810-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&22918-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&23031-59dc50c9-d658-44ce-b442-5a1f344d97c0; uids=eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=; KRTBCOOKIE_153=1923-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&19420-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&22979-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&23462-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse; KRTBCOOKIE_57=22776-41928985301451193&KRTB&23339-41928985301451193; KRTBCOOKIE_27=16735-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&16736-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23019-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23114-uid:3cab6283-4546-4500-a7b6-40ef605fe745; KRTBCOOKIE_18=22947-1978557989514665832; KRTBCOOKIE_466=16530-4fc36250-d852-459c-8772-7356de17ab97; KRTBCOOKIE_391=22924-8044608333778839078&KRTB&23263-8044608333778839078&KRTB&23481-8044608333778839078; KRTBCOOKIE_1310=23431-b81c3g7dr67i&KRTB&23446-b81c3g7dr67i&KRTB&23465-b81c3g7dr67i; KRTBCOOKIE_1290=23368-vkf3yv9lbbl; KRTBCOOKIE_22=14911-4554572065121110164&KRTB&23150-4554572065121110164; KRTBCOOKIE_860=16335-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23334-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23417-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23426-YGAqDU1zUTdjyAFxCoe3kctlNPo; KRTBCOOKIE_904=16787-KwJwE7NkCZClNJRysN2iYg; KRTBCOOKIE_1159=23138-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23328-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23427-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23445-5545f53f3d6e4ec199d8ed627ff026f3; KRTBCOOKIE_32=11175-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22713-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22715-AQEI_1QecY2ESAIjEW6KAQEBAQE; SyncRTB3=1685577600%3A35%7C1685491200%3A107_21_71_56_204_247_165_231_233_179_22_209_54_254_238_96_99_220_7_214_13_3_8_234_176_46_5%7C1684886400%3A2_223_15%7C1689465600%3A69%7C1685145600%3A63; KRTBCOOKIE_107=1471-uid:EK38R0PM1NQR0H5&KRTB&23421-uid:EK38R0PM1NQR0H5; KRTBCOOKIE_594=17105-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004&KRTB&17107-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004; SPugT=1684310122; chkChromeAb67Sec=133; KRTBCOOKIE_699=22727-AAFy2k7FBosAAEasbJoXnw; PugT=1684310473; origin=go-test`) + return r + }(), + Body: []byte(`{"imp":[{"tagid":"/43743431/DMDemo","ext":{"prebid":{"bidder":{"pubmatic":{"publisherId":"5890"}},"adunitcode":"div-gpt-ad-1460505748561-0"}},"id":"div-gpt-ad-1460505748561-0","banner":{"topframe":1,"format":[{"w":300,"h":250}]}}],"site":{"domain":"localhost:9999","publisher":{"domain":"localhost:9999","id":"5890"},"page":"http://localhost:9999/integrationExamples/gpt/owServer_example.html"},"device":{"w":1792,"h":446,"dnt":0,"ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36","language":"en","sua":{"source":1,"platform":{"brand":"macOS"},"browsers":[{"brand":"Google Chrome","version":["117"]},{"brand":"Not;A=Brand","version":["8"]},{"brand":"Chromium","version":["117"]}],"mobile":0}},"ext":{"prebid":{"auctiontimestamp":1697191822565,"targeting":{"includewinners":true,"includebidderkeys":true},"bidderparams":{"pubmatic":{"publisherId":"5890","wrapper":{}}},"channel":{"name":"pbjs","version":"v8.7.0-pre"},"createtids":false}},"id":"5bdd7da5-1166-40fe-a9cb-3bf3c3164cd3","test":0,"tmax":3000}`), + }, + setup: func(mme *mock_metrics.MockMetricsEngine) { + mme.EXPECT().RecordBadRequests(gomock.Any(), 700) + }, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + Reject: true, + NbrCode: nbr.InvalidProfileID, + Errors: []string{"ErrMissingProfileID"}, + }, + wantErr: nil, + }, + { + name: "/openrtb2/auction without source=pbjs/inapp. new hybrid endpoint should not execute module", + fields: fields{ + cfg: config.Config{}, + cache: nil, + }, + args: args{ + in0: context.Background(), + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb2/auction?debug=1", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("SOURCE_IP", "127.0.0.1") + r.Header.Add("Cookie", `KADUSERCOOKIE=7D75D25F-FAC9-443D-B2D1-B17FEE11E027; DPSync3=1684886400%3A248%7C1685491200%3A245_226_201; KRTBCOOKIE_80=16514-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&22987-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23025-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23386-CAESEMih0bN7ISRdZT8xX8LXzEw; KRTBCOOKIE_377=6810-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&22918-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&23031-59dc50c9-d658-44ce-b442-5a1f344d97c0; uids=eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=; KRTBCOOKIE_153=1923-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&19420-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&22979-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&23462-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse; KRTBCOOKIE_57=22776-41928985301451193&KRTB&23339-41928985301451193; KRTBCOOKIE_27=16735-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&16736-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23019-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23114-uid:3cab6283-4546-4500-a7b6-40ef605fe745; KRTBCOOKIE_18=22947-1978557989514665832; KRTBCOOKIE_466=16530-4fc36250-d852-459c-8772-7356de17ab97; KRTBCOOKIE_391=22924-8044608333778839078&KRTB&23263-8044608333778839078&KRTB&23481-8044608333778839078; KRTBCOOKIE_1310=23431-b81c3g7dr67i&KRTB&23446-b81c3g7dr67i&KRTB&23465-b81c3g7dr67i; KRTBCOOKIE_1290=23368-vkf3yv9lbbl; KRTBCOOKIE_22=14911-4554572065121110164&KRTB&23150-4554572065121110164; KRTBCOOKIE_860=16335-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23334-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23417-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23426-YGAqDU1zUTdjyAFxCoe3kctlNPo; KRTBCOOKIE_904=16787-KwJwE7NkCZClNJRysN2iYg; KRTBCOOKIE_1159=23138-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23328-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23427-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23445-5545f53f3d6e4ec199d8ed627ff026f3; KRTBCOOKIE_32=11175-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22713-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22715-AQEI_1QecY2ESAIjEW6KAQEBAQE; SyncRTB3=1685577600%3A35%7C1685491200%3A107_21_71_56_204_247_165_231_233_179_22_209_54_254_238_96_99_220_7_214_13_3_8_234_176_46_5%7C1684886400%3A2_223_15%7C1689465600%3A69%7C1685145600%3A63; KRTBCOOKIE_107=1471-uid:EK38R0PM1NQR0H5&KRTB&23421-uid:EK38R0PM1NQR0H5; KRTBCOOKIE_594=17105-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004&KRTB&17107-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004; SPugT=1684310122; chkChromeAb67Sec=133; KRTBCOOKIE_699=22727-AAFy2k7FBosAAEasbJoXnw; PugT=1684310473; origin=go-test`) + return r + }(), + Body: []byte(`{"imp":[{"tagid":"/43743431/DMDemo","ext":{"prebid":{"bidder":{"pubmatic":{"publisherId":"5890"}},"adunitcode":"div-gpt-ad-1460505748561-0"}},"id":"div-gpt-ad-1460505748561-0","banner":{"topframe":1,"format":[{"w":300,"h":250}]}}],"site":{"domain":"localhost:9999","publisher":{"domain":"localhost:9999","id":"5890"},"page":"http://localhost:9999/integrationExamples/gpt/owServer_example.html"},"device":{"w":1792,"h":446,"dnt":0,"ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36","language":"en","sua":{"source":1,"platform":{"brand":"macOS"},"browsers":[{"brand":"Google Chrome","version":["117"]},{"brand":"Not;A=Brand","version":["8"]},{"brand":"Chromium","version":["117"]}],"mobile":0}},"ext":{"prebid":{"auctiontimestamp":1697191822565,"targeting":{"includewinners":true,"includebidderkeys":true},"bidderparams":{"pubmatic":{"publisherId":"5890","wrapper":{}}},"channel":{"name":"pbjs","version":"v8.7.0-pre"},"createtids":false}},"id":"5bdd7da5-1166-40fe-a9cb-3bf3c3164cd3","test":0,"tmax":3000}`), + }, + setup: func(mme *mock_metrics.MockMetricsEngine) {}, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + Endpoint: models.EndpointHybrid, + }, + }, + }, + wantErr: nil, + }, + { + name: "hybrid api pbs/openrtb2/auction should not execute entrypointhook", + args: args{ + in0: context.Background(), + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/pbs/openrtb2/auction", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("SOURCE_IP", "127.0.0.1") + r.Header.Add("Cookie", `KADUSERCOOKIE=7D75D25F-FAC9-443D-B2D1-B17FEE11E027; DPSync3=1684886400%3A248%7C1685491200%3A245_226_201; KRTBCOOKIE_80=16514-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&22987-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23025-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23386-CAESEMih0bN7ISRdZT8xX8LXzEw; KRTBCOOKIE_377=6810-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&22918-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&23031-59dc50c9-d658-44ce-b442-5a1f344d97c0; uids=eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=; KRTBCOOKIE_153=1923-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&19420-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&22979-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&23462-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse; KRTBCOOKIE_57=22776-41928985301451193&KRTB&23339-41928985301451193; KRTBCOOKIE_27=16735-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&16736-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23019-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23114-uid:3cab6283-4546-4500-a7b6-40ef605fe745; KRTBCOOKIE_18=22947-1978557989514665832; KRTBCOOKIE_466=16530-4fc36250-d852-459c-8772-7356de17ab97; KRTBCOOKIE_391=22924-8044608333778839078&KRTB&23263-8044608333778839078&KRTB&23481-8044608333778839078; KRTBCOOKIE_1310=23431-b81c3g7dr67i&KRTB&23446-b81c3g7dr67i&KRTB&23465-b81c3g7dr67i; KRTBCOOKIE_1290=23368-vkf3yv9lbbl; KRTBCOOKIE_22=14911-4554572065121110164&KRTB&23150-4554572065121110164; KRTBCOOKIE_860=16335-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23334-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23417-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23426-YGAqDU1zUTdjyAFxCoe3kctlNPo; KRTBCOOKIE_904=16787-KwJwE7NkCZClNJRysN2iYg; KRTBCOOKIE_1159=23138-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23328-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23427-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23445-5545f53f3d6e4ec199d8ed627ff026f3; KRTBCOOKIE_32=11175-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22713-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22715-AQEI_1QecY2ESAIjEW6KAQEBAQE; SyncRTB3=1685577600%3A35%7C1685491200%3A107_21_71_56_204_247_165_231_233_179_22_209_54_254_238_96_99_220_7_214_13_3_8_234_176_46_5%7C1684886400%3A2_223_15%7C1689465600%3A69%7C1685145600%3A63; KRTBCOOKIE_107=1471-uid:EK38R0PM1NQR0H5&KRTB&23421-uid:EK38R0PM1NQR0H5; KRTBCOOKIE_594=17105-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004&KRTB&17107-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004; SPugT=1684310122; chkChromeAb67Sec=133; KRTBCOOKIE_699=22727-AAFy2k7FBosAAEasbJoXnw; PugT=1684310473; origin=go-test`) + return r + }(), + Body: []byte(`{"ext":{"wrapper":{"profileid":5890,"versionid":1}}}`), + }, + setup: func(mme *mock_metrics.MockMetricsEngine) {}, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + Endpoint: models.EndpointHybrid, + }, + }, + }, + wantErr: nil, + }, + { + name: "valid /openrtb2/auction?source=inapp request directly on port 8000", + fields: fields{ + cfg: config.Config{ + Tracker: config.Tracker{ + Endpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + }, + }, + cache: nil, + }, + args: args{ + in0: context.Background(), + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb2/auction?source=inapp&debug=1", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("SOURCE_IP", "127.0.0.1") + r.Header.Add("Cookie", `KADUSERCOOKIE=7D75D25F-FAC9-443D-B2D1-B17FEE11E027; DPSync3=1684886400%3A248%7C1685491200%3A245_226_201; KRTBCOOKIE_80=16514-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&22987-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23025-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23386-CAESEMih0bN7ISRdZT8xX8LXzEw; KRTBCOOKIE_377=6810-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&22918-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&23031-59dc50c9-d658-44ce-b442-5a1f344d97c0; uids=eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=; KRTBCOOKIE_153=1923-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&19420-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&22979-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&23462-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse; KRTBCOOKIE_57=22776-41928985301451193&KRTB&23339-41928985301451193; KRTBCOOKIE_27=16735-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&16736-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23019-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23114-uid:3cab6283-4546-4500-a7b6-40ef605fe745; KRTBCOOKIE_18=22947-1978557989514665832; KRTBCOOKIE_466=16530-4fc36250-d852-459c-8772-7356de17ab97; KRTBCOOKIE_391=22924-8044608333778839078&KRTB&23263-8044608333778839078&KRTB&23481-8044608333778839078; KRTBCOOKIE_1310=23431-b81c3g7dr67i&KRTB&23446-b81c3g7dr67i&KRTB&23465-b81c3g7dr67i; KRTBCOOKIE_1290=23368-vkf3yv9lbbl; KRTBCOOKIE_22=14911-4554572065121110164&KRTB&23150-4554572065121110164; KRTBCOOKIE_860=16335-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23334-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23417-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23426-YGAqDU1zUTdjyAFxCoe3kctlNPo; KRTBCOOKIE_904=16787-KwJwE7NkCZClNJRysN2iYg; KRTBCOOKIE_1159=23138-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23328-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23427-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23445-5545f53f3d6e4ec199d8ed627ff026f3; KRTBCOOKIE_32=11175-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22713-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22715-AQEI_1QecY2ESAIjEW6KAQEBAQE; SyncRTB3=1685577600%3A35%7C1685491200%3A107_21_71_56_204_247_165_231_233_179_22_209_54_254_238_96_99_220_7_214_13_3_8_234_176_46_5%7C1684886400%3A2_223_15%7C1689465600%3A69%7C1685145600%3A63; KRTBCOOKIE_107=1471-uid:EK38R0PM1NQR0H5&KRTB&23421-uid:EK38R0PM1NQR0H5; KRTBCOOKIE_594=17105-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004&KRTB&17107-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004; SPugT=1684310122; chkChromeAb67Sec=133; KRTBCOOKIE_699=22727-AAFy2k7FBosAAEasbJoXnw; PugT=1684310473; origin=go-test`) + return r + }(), + Body: []byte(`{"ext":{"wrapper":{"profileid":5890,"versionid":1}}}`), + }, + setup: func(mme *mock_metrics.MockMetricsEngine) {}, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + ProfileID: 5890, + DisplayID: 1, + DisplayVersionID: 1, + SSAuction: -1, + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: false, + TrackerEndpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + UidCookie: &http.Cookie{ + Name: "uids", + Value: `eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=`, + }, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: `7D75D25F-FAC9-443D-B2D1-B17FEE11E027`, + }, + OriginCookie: "go-test", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: "5890", + Endpoint: models.EndpointV25, + MetricsEngine: mockEngine, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + }, + }, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + tt.args.setup(mockEngine) + m := OpenWrap{ + cfg: tt.fields.cfg, + cache: tt.fields.cache, + metricEngine: mockEngine, + } + got, err := m.handleEntrypointHook(tt.args.in0, tt.args.miCtx, tt.args.payload) + assert.Equal(t, err, tt.wantErr) + + if tt.want.ModuleContext != nil { + // validate runtime values individually and reset them + gotRctx := got.ModuleContext["rctx"].(models.RequestCtx) + if gotRctx.Endpoint == models.EndpointHybrid || gotRctx.Sshb == "1" { + assert.Equal(t, got, tt.want) + return + } + + assert.NotEmpty(t, gotRctx.StartTime) + gotRctx.StartTime = 0 + + wantRctx := tt.want.ModuleContext["rctx"].(models.RequestCtx) + if wantRctx.LoggerImpressionID == "" { + assert.Len(t, gotRctx.LoggerImpressionID, 36) + gotRctx.LoggerImpressionID = "" + } + gotRctx.ParsedUidCookie = nil // ignore parsed cookies + gotRctx.CurrencyConversion = nil // ignore currency-conversion + got.ModuleContext["rctx"] = gotRctx + } + assert.Equal(t, got, tt.want) + }) + } +} diff --git a/modules/pubmatic/openwrap/floors.go b/modules/pubmatic/openwrap/floors.go new file mode 100644 index 00000000000..89f993c4524 --- /dev/null +++ b/modules/pubmatic/openwrap/floors.go @@ -0,0 +1,53 @@ +package openwrap + +import ( + "strings" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" +) + +func setFloorsExt(requestExt *models.RequestExt, configMap map[int]map[string]string) { + if configMap == nil || configMap[models.VersionLevelConfigID] == nil { + return + } + + if requestExt != nil && requestExt.Prebid.Floors != nil && requestExt.Prebid.Floors.Enabled != nil && !(*requestExt.Prebid.Floors.Enabled) { + return + } + + if requestExt.Prebid.Floors == nil { + requestExt.Prebid.Floors = new(openrtb_ext.PriceFloorRules) + } + if requestExt.Prebid.Floors.Enabled == nil { + requestExt.Prebid.Floors.Enabled = ptrutil.ToPtr(true) + + } + + if requestExt.Prebid.Floors.Enforcement == nil { + requestExt.Prebid.Floors.Enforcement = new(openrtb_ext.PriceFloorEnforcement) + } + + if requestExt.Prebid.Floors.Enforcement.EnforcePBS == nil { + // By default enforcemnt will be true i.e hard floor + requestExt.Prebid.Floors.Enforcement.EnforcePBS = ptrutil.ToPtr(true) + + floorType, typeExists := configMap[models.VersionLevelConfigID][models.FloorType] + if typeExists && strings.ToLower(floorType) == models.SoftFloorType { + *requestExt.Prebid.Floors.Enforcement.EnforcePBS = false + } + } + + // Based on floorPriceModuleEnabled flag, dynamic fetch would be enabled/disabled + enableFlag, isFlagPresent := configMap[models.VersionLevelConfigID][models.FloorModuleEnabled] + if isFlagPresent && enableFlag == "1" { + url, urlExists := configMap[models.VersionLevelConfigID][models.PriceFloorURL] + if urlExists { + if requestExt.Prebid.Floors.Location == nil { + requestExt.Prebid.Floors.Location = new(openrtb_ext.PriceFloorEndpoint) + } + requestExt.Prebid.Floors.Location.URL = url + } + } +} diff --git a/modules/pubmatic/openwrap/floors_test.go b/modules/pubmatic/openwrap/floors_test.go new file mode 100644 index 00000000000..50c43b4ad4f --- /dev/null +++ b/modules/pubmatic/openwrap/floors_test.go @@ -0,0 +1,358 @@ +package openwrap + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestSetFloorsExt(t *testing.T) { + enable := true + disable := false + + type args struct { + requestExt *models.RequestExt + configMap map[int]map[string]string + } + tests := []struct { + name string + args args + want *models.RequestExt + }{ + { + name: "Only JSON URL is present in db", + args: args{ + requestExt: &models.RequestExt{}, + configMap: map[int]map[string]string{ + -1: { + "jsonUrl": "http://test.com/floor", + }, + }, + }, + want: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &enable, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: &enable, + }, + }, + }, + }, + }, + }, + { + name: "JSON URL is present in request, but floor module disabed", + args: args{ + requestExt: &models.RequestExt{}, + configMap: map[int]map[string]string{ + -1: { + "jsonUrl": "http://test.com/floor", + "floorPriceModuleEnabled": "0", + }, + }, + }, + want: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &enable, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: &enable, + }, + }, + }, + }, + }, + }, + { + name: "JSON URL not present in db, but floor module enabled", + args: args{ + requestExt: &models.RequestExt{}, + configMap: map[int]map[string]string{ + -1: { + "floorPriceModuleEnabled": "1", + }, + }, + }, + want: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &enable, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: &enable, + }, + }, + }, + }, + }, + }, + { + name: "JSON URL not present in request, floor module enabled is not present", + args: args{ + requestExt: &models.RequestExt{}, + configMap: map[int]map[string]string{ + -1: {}, + }, + }, + want: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &enable, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: &enable, + }, + }, + }, + }, + }, + }, + { + name: "Request has floor disabled, db has fetch url and floor module enabled", + args: args{ + requestExt: func() *models.RequestExt { + disable := false + res := models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &disable, + }, + }, + }, + } + return &res + }(), + configMap: map[int]map[string]string{ + -1: { + "jsonUrl": "http://test.com/floor", + "floorPriceModuleEnabled": "1", + }, + }, + }, + want: func() *models.RequestExt { + disable := false + res := models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &disable, + }, + }, + }, + } + return &res + }(), + }, + { + name: "Request has floor enabled, db has fetch url and floor module enabled", + args: args{ + requestExt: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &enable, + }, + }, + }, + }, + configMap: map[int]map[string]string{ + -1: { + "jsonUrl": "http://test.com/floor", + "floorPriceModuleEnabled": "1", + }, + }, + }, + want: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &enable, + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "http://test.com/floor", + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: &enable, + }, + }, + }, + }, + }, + }, + { + name: "Request has floor enabled, db has fetch url and floor module disabled", + args: args{ + requestExt: func() *models.RequestExt { + enable := true + r := models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &enable, + }, + }, + }, + } + return &r + }(), + configMap: map[int]map[string]string{ + -1: { + "jsonUrl": "http://test.com/floor", + "floorPriceModuleEnabled": "0", + }, + }, + }, + want: func() *models.RequestExt { + enable := true + res := models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &enable, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: &enable, + }, + }, + }, + }, + } + return &res + }(), + }, + { + name: "Request is empty, db has floortype as soft", + args: args{ + requestExt: &models.RequestExt{}, + configMap: map[int]map[string]string{ + -1: { + "floorType": "Soft", + }, + }, + }, + want: func() *models.RequestExt { + enable := true + disable := false + res := models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &enable, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: &disable, + }, + }, + }, + }, + } + return &res + }(), + }, + { + name: "Request is empty, db has floortype as hard", + args: args{ + requestExt: &models.RequestExt{}, + configMap: map[int]map[string]string{ + -1: { + "floorType": "Hard", + }, + }, + }, + want: func() *models.RequestExt { + enable := true + res := models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &enable, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: &enable, + }, + }, + }, + }, + } + return &res + }(), + }, + { + name: "Request has EnforcePBS false, db has floortype as hard", + args: args{ + requestExt: func() *models.RequestExt { + disable := false + res := models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: &disable, + }, + }, + }, + }, + } + return &res + }(), + configMap: map[int]map[string]string{ + -1: { + "floorType": "Hard", + }, + }, + }, + want: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &enable, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: &disable, + }, + }, + }, + }, + }, + }, + { + name: "Request has floors disabled, db has floortype as hard, enforcepbs will be nil", + args: args{ + requestExt: func() *models.RequestExt { + disable := false + res := models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &disable, + }, + }, + }, + } + return &res + }(), + configMap: map[int]map[string]string{ + -1: { + "floorType": "Hard", + }, + }, + }, + want: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Enabled: &disable, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setFloorsExt(tt.args.requestExt, tt.args.configMap) + assert.Equal(t, tt.want, tt.args.requestExt) + }) + } +} diff --git a/modules/pubmatic/openwrap/fullscreenclickability/fullscreenclickability.go b/modules/pubmatic/openwrap/fullscreenclickability/fullscreenclickability.go new file mode 100644 index 00000000000..8992f1e569b --- /dev/null +++ b/modules/pubmatic/openwrap/fullscreenclickability/fullscreenclickability.go @@ -0,0 +1,103 @@ +package fullscreenclickability + +import ( + "math/rand" + + "sync" + + "github.com/golang/glog" + cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +type fsc struct { + cache cache.Cache + disabledPublishers map[int]struct{} + thresholdsPerDsp map[int]int + sync.RWMutex + serviceStop chan struct{} +} + +var fscConfigs fsc + +// Initializing reloader with cache-refresh default-expiry + 30 mins (to avoid DB load post cache refresh) +func Init(c cache.Cache, defaultExpiry int) { + //init fsc configs + fscConfigs = fsc{ + cache: c, + disabledPublishers: make(map[int]struct{}), + thresholdsPerDsp: make(map[int]int), + serviceStop: make(chan struct{}), + } + + go initiateReloader(c, defaultExpiry+1800) + glog.Info("Initialized FSC cache update reloaders for publisher and dsp fsc configuraitons") + +} + +// Exposed to access fsc object +func GetFscInstance() *fsc { + return &fscConfigs +} + +/* +IsUnderFSCThreshold:- returns fsc 1/0 based on: +1. When publisher has disabled FSC in DB, return 0 +2. If FSC is enabled for publisher(default), consider DSP-threshold , and predict value of fsc 0 or 1. +3. If dspId is not present return 0 +*/ +func (f *fsc) IsUnderFSCThreshold(pubid int, dspid int) int { + f.RLock() + defer f.RUnlock() + + if _, isPresent := f.disabledPublishers[pubid]; isPresent { + return 0 + } + + if dspThreshold, isPresent := f.thresholdsPerDsp[dspid]; isPresent && predictFscValue(dspThreshold) { + return 1 + } + return 0 +} + +func predictFscValue(threshold int) bool { + return (rand.Intn(100)) < threshold +} + +// fetch and update fsc config maps from DB +func updateFscConfigMapsFromCache(c cache.Cache) { + var err error + disabledPublishers, errPubFsc := c.GetFSCDisabledPublishers() + if errPubFsc != nil { + err = models.ErrorWrap(err, errPubFsc) + } + thresholdsPerDsp, errDspFsc := c.GetFSCThresholdPerDSP() + if errDspFsc != nil { + err = models.ErrorWrap(err, errDspFsc) + } + if err != nil { + glog.Error(err.Error()) + return + } + fscConfigs.Lock() + fscConfigs.disabledPublishers = disabledPublishers + fscConfigs.thresholdsPerDsp = thresholdsPerDsp + fscConfigs.Unlock() +} + +// IsFscApplicable returns true if fsc can be applied (fsc=1) +func IsFscApplicable(pubId int, seat string, dspId int) bool { + return models.IsPubmaticCorePartner(seat) && (fscConfigs.IsUnderFSCThreshold(pubId, dspId) != 0) +} + +// Exposed for test cases +func SetAndResetFscWithMockCache(mockDb cache.Cache, dspThresholdMap map[int]int) func() { + fscConfigs.cache = mockDb + //mockDspID entry for testing fsc=1 + fscConfigs.thresholdsPerDsp = dspThresholdMap + return func() { + fscConfigs.cache = nil + fscConfigs.thresholdsPerDsp = make(map[int]int) + fscConfigs.disabledPublishers = make(map[int]struct{}) + } +} diff --git a/modules/pubmatic/openwrap/fullscreenclickability/fullscreenclickability_test.go b/modules/pubmatic/openwrap/fullscreenclickability/fullscreenclickability_test.go new file mode 100644 index 00000000000..a29b48f4d7c --- /dev/null +++ b/modules/pubmatic/openwrap/fullscreenclickability/fullscreenclickability_test.go @@ -0,0 +1,297 @@ +package fullscreenclickability + +import ( + "errors" + + "reflect" + "testing" + + cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + mock_dbcache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache/mock" + + "github.com/golang/mock/gomock" +) + +func TestPredictFscValue(t *testing.T) { + type args struct { + percentage int + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "getting from predict output", + args: args{ + percentage: 100, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := predictFscValue(tt.args.percentage); got != tt.want { + t.Errorf("predictFscValue() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestInit(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockCache := mock_dbcache.NewMockCache(ctrl) + var defCpy = initiateReloader + initiateReloader = func(c cache.Cache, expiryTime int) {} + defer func() { + initiateReloader = defCpy + }() + type args struct { + defaultExpiry int + cache cache.Cache + } + tests := []struct { + name string + args args + }{ + {name: "test Init with valid args", + args: args{defaultExpiry: 1, + cache: mockCache, + }, + }, + } + for _, tt := range tests { + Init(tt.args.cache, tt.args.defaultExpiry) + } + +} + +func TestIsUnderFSCThreshold(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockCache := mock_dbcache.NewMockCache(ctrl) + type fields struct { + cache cache.Cache + disabledPublishers map[int]struct{} + thresholdsPerDsp map[int]int + } + type args struct { + pubid int + dspid int + } + tests := []struct { + name string + fields fields + args args + want int + setup func() + }{ + { + name: "When pubId,dspid and FSC maps are valid, pubID enabled(default) FSC return fsc with prediction algo", + args: args{ + pubid: 5890, + dspid: 6, + }, + setup: func() { + + }, + fields: fields{cache: mockCache, + disabledPublishers: map[int]struct{}{ + 58903: {}, + }, + thresholdsPerDsp: map[int]int{6: 100}, + }, + + want: 1, + }, + { + name: "When pubId,dspid and FSC maps are valid, pubID disabled FSC return fsc=0", + args: args{ + pubid: 5890, + dspid: 6, + }, + setup: func() { + + }, + fields: fields{cache: mockCache, + disabledPublishers: map[int]struct{}{ + 5890: {}, + }, + thresholdsPerDsp: map[int]int{6: 100}}, + want: 0, + }, + { + name: "When pubId,dspid are not present, pubID disabled FSC return fsc=0", + args: args{ + pubid: 58907, + dspid: 90, + }, + setup: func() { + + }, + fields: fields{cache: mockCache, + disabledPublishers: map[int]struct{}{ + 5890: {}, + }, + thresholdsPerDsp: map[int]int{6: 100}}, + want: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := fsc{ + cache: tt.fields.cache, + disabledPublishers: tt.fields.disabledPublishers, + thresholdsPerDsp: tt.fields.thresholdsPerDsp, + } + tt.setup() + if got := f.IsUnderFSCThreshold(tt.args.pubid, tt.args.dspid); got != tt.want { + t.Errorf("fsc.IsUnderFSCThreshold() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUpdateFscConfigMapsFromCache(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockCache := mock_dbcache.NewMockCache(ctrl) + type args struct { + cache cache.Cache + } + type wantMaps struct { + fscDsp map[int]int + fscPub map[int]struct{} + } + tests := []struct { + name string + setup func() + args args + want wantMaps + }{ + { + name: "Cache returns valid values, set in fscConfigs Maps", + args: args{ + cache: mockCache, + }, + want: wantMaps{ + fscDsp: map[int]int{6: 70}, + fscPub: map[int]struct{}{ + 5890: {}}, + }, + setup: func() { + mockCache.EXPECT().GetFSCDisabledPublishers().Return(map[int]struct{}{5890: {}}, nil) + mockCache.EXPECT().GetFSCThresholdPerDSP().Return(map[int]int{6: 70}, nil) + }, + }, + { + name: "Cache returns DB error for both DSP and PUB fsc configs", + args: args{ + cache: mockCache, + }, + want: wantMaps{ + fscDsp: map[int]int{}, + fscPub: map[int]struct{}{}, + }, + setup: func() { + mockCache.EXPECT().GetFSCDisabledPublishers().Return(map[int]struct{}{}, errors.New("QUERY FAILED")) + mockCache.EXPECT().GetFSCThresholdPerDSP().Return(map[int]int{}, errors.New("QUERY FAILED")) + }, + }, + } + for _, tt := range tests { + + t.Run(tt.name, func(t *testing.T) { + tt.setup() + + updateFscConfigMapsFromCache(tt.args.cache) + if !reflect.DeepEqual(fscConfigs.disabledPublishers, tt.want.fscPub) { + t.Errorf("updateFscConfigMapsFromCache() for FscDisabledPublishers = %v, %v", fscConfigs.disabledPublishers, tt.want.fscPub) + } + if !reflect.DeepEqual(fscConfigs.thresholdsPerDsp, tt.want.fscDsp) { + t.Errorf("updateFscConfigMapsFromCache() for FscDspThresholds= %v, %v", fscConfigs.disabledPublishers, tt.want.fscDsp) + } + SetAndResetFscWithMockCache(mockCache, nil)() + }) + } +} + +func TestGetFscInstance(t *testing.T) { + tests := []struct { + name string + want *fsc + }{ + {name: "Return single FSC instance"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetFscInstance(); reflect.TypeOf(got) == reflect.TypeOf(fsc{}) { + t.Errorf("GetFscInstance() gotType = %v, wantedType %v", reflect.TypeOf(got), reflect.TypeOf(fsc{})) + } + }) + } +} + +func TestIsFscApplicable(t *testing.T) { + ctrl := gomock.NewController(t) + mockCache := mock_dbcache.NewMockCache(ctrl) + defer ctrl.Finish() + defer SetAndResetFscWithMockCache(mockCache, map[int]int{6: 100})() + + type args struct { + pubId int + seat string + dspId int + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Valid Case1: All Params Correct", + args: args{ + pubId: 5890, + seat: "pubmatic", + dspId: 6, + }, + want: true, + }, + { + name: "Valid Case2: All Params Correct, seat is pubmatic alaias", + args: args{ + pubId: 5890, + seat: "pubmatic2", + dspId: 6, + }, + want: true, + }, + { + name: "Invalid Case1: DspId is 0", + args: args{ + pubId: 5890, + seat: "pubmatic", + dspId: 0, + }, + want: false, + }, + { + name: "Invalid Case2: different seat ", + args: args{ + pubId: 5890, + seat: "appnexus", + dspId: 6, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + if got := IsFscApplicable(tt.args.pubId, tt.args.seat, tt.args.dspId); got != tt.want { + t.Errorf("isFscApplicable() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/fullscreenclickability/reloader.go b/modules/pubmatic/openwrap/fullscreenclickability/reloader.go new file mode 100644 index 00000000000..67842c2b1b7 --- /dev/null +++ b/modules/pubmatic/openwrap/fullscreenclickability/reloader.go @@ -0,0 +1,37 @@ +package fullscreenclickability + +import ( + "time" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" +) + +// These reloaders will be called only to Forced-Write into Cache post timer based call. +var initiateReloader = func(c cache.Cache, expiryTime int) { + if expiryTime <= 0 { + return + } + glog.Info("FSC Reloader start") + ticker := time.NewTicker(time.Duration(expiryTime) * time.Second) + for { + //Populating FscConfigMaps + updateFscConfigMapsFromCache(c) + select { + case t := <-ticker.C: + glog.Info("FSC Reloader loads cache @", t) + case <-fscConfigs.serviceStop: + return + } + } +} + +func StopReloaderService() { + //updating serviceStop flag to true + close(fscConfigs.serviceStop) +} + +func ResetInitFscReloaderTest() { + //setting empty to mock routine + initiateReloader = func(c cache.Cache, expiryTime int) {} +} diff --git a/modules/pubmatic/openwrap/fullscreenclickability/reloader_test.go b/modules/pubmatic/openwrap/fullscreenclickability/reloader_test.go new file mode 100644 index 00000000000..c81b4a28fef --- /dev/null +++ b/modules/pubmatic/openwrap/fullscreenclickability/reloader_test.go @@ -0,0 +1,58 @@ +package fullscreenclickability + +import ( + "testing" + "time" + + "github.com/golang/mock/gomock" + cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + mock_dbcache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache/mock" +) + +func TestInitiateReloader(t *testing.T) { + ctrl := gomock.NewController(t) + mockCache := mock_dbcache.NewMockCache(ctrl) + defer SetAndResetFscWithMockCache(mockCache, nil)() + currentChan := fscConfigs.serviceStop + defer func() { + ctrl.Finish() + fscConfigs.serviceStop = currentChan + }() + fscConfigs.serviceStop = make(chan struct{}) + type args struct { + defaultExpiry int + cache cache.Cache + } + + tests := []struct { + name string + args args + setup func() + }{ + { + name: "test InitateReloader with valid cache and invalid time, exit", + args: args{defaultExpiry: 0, + cache: mockCache, + }, + setup: func() {}, + }, + { + name: "test InitateReloader with valid cache and time, call once and exit", + args: args{defaultExpiry: 1000, + cache: mockCache, + }, + setup: func() { + mockCache.EXPECT().GetFSCDisabledPublishers().Return(map[int]struct{}{}, nil) + mockCache.EXPECT().GetFSCThresholdPerDSP().Return(map[int]int{}, nil) + }, + }, + } + for _, tt := range tests { + tt.setup() + fscConfigs.serviceStop = make(chan struct{}) + go initiateReloader(tt.args.cache, tt.args.defaultExpiry) + //closing channel to avoid infinite loop + StopReloaderService() + time.Sleep(1 * time.Millisecond) + } +} diff --git a/modules/pubmatic/openwrap/logger.go b/modules/pubmatic/openwrap/logger.go new file mode 100644 index 00000000000..c3cfc666b62 --- /dev/null +++ b/modules/pubmatic/openwrap/logger.go @@ -0,0 +1,51 @@ +package openwrap + +import ( + "encoding/json" + "fmt" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +func getIncomingSlots(imp openrtb2.Imp) []string { + sizes := map[string]struct{}{} + if imp.Banner == nil && imp.Video == nil && imp.Native != nil { + return []string{"1x1"} + } + if imp.Banner != nil { + if imp.Banner.W != nil && imp.Banner.H != nil { + sizes[fmt.Sprintf("%dx%d", *imp.Banner.W, *imp.Banner.H)] = struct{}{} + } + + for _, format := range imp.Banner.Format { + sizes[fmt.Sprintf("%dx%d", format.W, format.H)] = struct{}{} + } + } + + if imp.Video != nil { + sizes[fmt.Sprintf("%dx%dv", imp.Video.W, imp.Video.H)] = struct{}{} + } + + var s []string + for k := range sizes { + s = append(s, k) + } + return s +} + +func getDefaultImpBidCtx(request openrtb2.BidRequest) map[string]models.ImpCtx { + impBidCtx := make(map[string]models.ImpCtx) + for _, imp := range request.Imp { + impExt := &models.ImpExtension{} + json.Unmarshal(imp.Ext, impExt) + + impBidCtx[imp.ID] = models.ImpCtx{ + IncomingSlots: getIncomingSlots(imp), + AdUnitName: getAdunitName(imp.TagID, impExt), + SlotName: getSlotName(imp.TagID, impExt), + IsRewardInventory: impExt.Reward, + } + } + return impBidCtx +} diff --git a/modules/pubmatic/openwrap/marketplace.go b/modules/pubmatic/openwrap/marketplace.go new file mode 100644 index 00000000000..0aca9b8239e --- /dev/null +++ b/modules/pubmatic/openwrap/marketplace.go @@ -0,0 +1,37 @@ +package openwrap + +import ( + "strings" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// TODO: Make this generic implementation +func getMarketplaceBidders(reqABC *openrtb_ext.ExtAlternateBidderCodes, partnerConfigMap map[int]map[string]string) (*openrtb_ext.ExtAlternateBidderCodes, map[string]struct{}) { + if reqABC != nil { + return reqABC, nil + } + + // string validations, etc will be done by api-wrapper-tag. Not need to repetitively do the typical string validations + marketplaceBiddersDB := partnerConfigMap[models.VersionLevelConfigID][models.MarketplaceBidders] + if len(marketplaceBiddersDB) == 0 { + return nil, nil + } + marketplaceBidders := strings.Split(marketplaceBiddersDB, ",") + + bidderMap := make(map[string]struct{}) + for _, bidder := range marketplaceBidders { + bidderMap[bidder] = struct{}{} + } + + return &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + string(openrtb_ext.BidderPubmatic): { // How do we get non-pubmatic bidders?. Does core-platform even have it? + Enabled: true, + AllowedBidderCodes: marketplaceBidders, + }, + }, + }, bidderMap +} diff --git a/modules/pubmatic/openwrap/marketplace_test.go b/modules/pubmatic/openwrap/marketplace_test.go new file mode 100644 index 00000000000..35048244692 --- /dev/null +++ b/modules/pubmatic/openwrap/marketplace_test.go @@ -0,0 +1,143 @@ +package openwrap + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestGetMarketplaceBidders(t *testing.T) { + type args struct { + reqABC *openrtb_ext.ExtAlternateBidderCodes + partnerConfigMap map[int]map[string]string + } + type want struct { + alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes + marketPlaceBidders map[string]struct{} + } + tests := []struct { + name string + args args + want want + }{ + { + name: "happy_path,_marketplace_enabled_in_profile,_alternatebiddercodes_ext_should_be_build_using_profile_version_data", + args: args{ + reqABC: nil, + partnerConfigMap: map[int]map[string]string{ + models.VersionLevelConfigID: { + models.MarketplaceBidders: "pubmatic,groupm", + }, + }, + }, + want: want{ + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + models.BidderPubMatic: { + Enabled: true, + AllowedBidderCodes: []string{"pubmatic", "groupm"}, + }, + }, + }, + marketPlaceBidders: map[string]struct{}{ + "pubmatic": {}, + "groupm": {}, + }, + }, + }, + { + name: "pubmatic_not_present_in_profile_level_data,_alternatebiddercodes_ext_with_addition_of_pubmatic_bidder_should_be_build_using_profile_version_data", + args: args{ + reqABC: nil, + partnerConfigMap: map[int]map[string]string{ + models.VersionLevelConfigID: { + models.MarketplaceBidders: "groupm", + }, + }, + }, + want: want{ + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + models.BidderPubMatic: { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }, + marketPlaceBidders: map[string]struct{}{ + "groupm": {}, + }, + }, + }, + { + name: "empty_incoming_alternatebiddercodes_ext_priority_should_be_given_to_request", + args: args{ + reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, + partnerConfigMap: map[int]map[string]string{ + models.VersionLevelConfigID: { + models.MarketplaceBidders: "pubmatic,groupm", + }, + }, + }, + want: want{ + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{}, + marketPlaceBidders: nil, + }, + }, + { + name: "incoming_request_has_alternatebiddercodes,_marketplace_enabled_in_profile,_request_data_has_priority", + args: args{ + reqABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + models.BidderPubMatic: { + Enabled: true, + AllowedBidderCodes: []string{"pubmatic", "appnexus"}, + }, + }, + }, + partnerConfigMap: map[int]map[string]string{ + models.VersionLevelConfigID: { + models.MarketplaceBidders: "pubmatic,groupm", + }, + }, + }, + want: want{ + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + models.BidderPubMatic: { + Enabled: true, + AllowedBidderCodes: []string{"pubmatic", "appnexus"}, + }, + }, + }, + marketPlaceBidders: nil, + }, + }, + { + name: "marketplace_not_enabled_in profile,_alternatebiddercodes_ext_should_be_nil", + args: args{ + reqABC: nil, + partnerConfigMap: map[int]map[string]string{ + models.VersionLevelConfigID: {"k1": "v1"}, + }, + }, + want: want{ + alternateBidderCodes: nil, + marketPlaceBidders: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + alternateBidderCodes, marketPlaceBidders := getMarketplaceBidders(tt.args.reqABC, tt.args.partnerConfigMap) + assert.Equal(t, tt.want.alternateBidderCodes, alternateBidderCodes) + assert.Equal(t, tt.want.marketPlaceBidders, marketPlaceBidders) + }) + } +} diff --git a/modules/pubmatic/openwrap/matchedimpression.go b/modules/pubmatic/openwrap/matchedimpression.go new file mode 100644 index 00000000000..64c92102536 --- /dev/null +++ b/modules/pubmatic/openwrap/matchedimpression.go @@ -0,0 +1,42 @@ +package openwrap + +import ( + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adapters" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +func getMatchedImpression(rctx models.RequestCtx) map[string]int { + + cookieFlagMap := make(map[string]int) + for _, partnerConfig := range rctx.PartnerConfigMap { + if partnerConfig[models.SERVER_SIDE_FLAG] != "1" { + continue + } + + syncerMap := models.SyncerMap + partnerName := partnerConfig[models.PREBID_PARTNER_NAME] + + syncerCode := adapters.ResolveOWBidder(partnerName) + + matchedImpression := 0 + + syncer := syncerMap[syncerCode] + if syncer == nil { + glog.V(3).Infof("Invalid bidder code passed to ParseRequestCookies: %s ", partnerName) + } else { + uid, _, _ := rctx.ParsedUidCookie.GetUID(syncer.Key()) + + // Added flag in map for Cookie is present + // we are not considering if the cookie is active + if uid != "" { + matchedImpression = 1 + } + } + cookieFlagMap[partnerConfig[models.BidderCode]] = matchedImpression + if matchedImpression == 0 { + rctx.MetricsEngine.RecordPublisherPartnerNoCookieStats(rctx.PubIDStr, partnerConfig[models.BidderCode]) + } + } + return cookieFlagMap +} diff --git a/modules/pubmatic/openwrap/metrics/config/metrics.go b/modules/pubmatic/openwrap/metrics/config/metrics.go new file mode 100644 index 00000000000..01cc72f47a7 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/config/metrics.go @@ -0,0 +1,447 @@ +package config + +import ( + "fmt" + "time" + + cfg "github.com/prebid/prebid-server/v2/config" + metrics_cfg "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + ow_prometheus "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/prometheus" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/stats" + "github.com/prometheus/client_golang/prometheus" +) + +// NewMetricsEngine initialises the stats-client and prometheus and return them as MultiMetricsEngine +func NewMetricsEngine(cfg *config.Config, metricsCfg *cfg.Metrics, metricsRegistry metrics_cfg.MetricsRegistry) (MultiMetricsEngine, error) { + + // Create a list of metrics engines to use. + engineList := make(MultiMetricsEngine, 0, 2) + + if cfg.Stats.Endpoint != "" { + hostName := cfg.Stats.DefaultHostName // Dummy hostname N:P + if cfg.Stats.UseHostName { + hostName = cfg.Server.HostName // actual hostname node-name:pod-name + } + + sc, err := stats.InitStatsClient( + cfg.Stats.Endpoint, + hostName, + cfg.Server.HostName, + cfg.Server.DCName, + cfg.Stats.PublishInterval, + cfg.Stats.PublishThreshold, + cfg.Stats.Retries, + cfg.Stats.DialTimeout, + cfg.Stats.KeepAliveDuration, + cfg.Stats.MaxIdleConnections, + cfg.Stats.MaxIdleConnectionsPerHost, + cfg.Stats.ResponseHeaderTimeout, + cfg.Stats.MaxChannelLength, + cfg.Stats.PoolMaxWorkers, + cfg.Stats.PoolMaxCapacity) + + if err != nil { + return nil, err + } + + engineList = append(engineList, sc) + } + + // Set up the Prometheus metrics engine. + if metricsCfg != nil && metricsRegistry != nil && metricsRegistry[metrics_cfg.PrometheusRegistry] != nil { + prometheusRegistry, ok := metricsRegistry[metrics_cfg.PrometheusRegistry].(*prometheus.Registry) + if ok && prometheusRegistry != nil { + prometheusEngine := ow_prometheus.NewMetrics(&metricsCfg.Prometheus, prometheusRegistry) + engineList = append(engineList, prometheusEngine) + } + } + + if len(engineList) > 0 { + return engineList, nil + } + return nil, fmt.Errorf("metric-engine is not configured") +} + +// MultiMetricsEngine logs metrics to multiple metrics databases These can be useful in transitioning +// an instance from one engine to another, you can run both in parallel to verify stats match up. +type MultiMetricsEngine []metrics.MetricsEngine + +// RecordOpenWrapServerPanicStats across all engines +func (me *MultiMetricsEngine) RecordOpenWrapServerPanicStats(host, method string) { + for _, thisME := range *me { + thisME.RecordOpenWrapServerPanicStats(host, method) + } +} + +// RecordPublisherPartnerNoCookieStats across all engines +func (me *MultiMetricsEngine) RecordPublisherPartnerNoCookieStats(publisher, partner string) { + for _, thisME := range *me { + thisME.RecordPublisherPartnerNoCookieStats(publisher, partner) + } +} + +// RecordPartnerTimeoutErrorStats across all engines +func (me *MultiMetricsEngine) RecordPartnerResponseErrors(publisher, partner, err string) { + for _, thisME := range *me { + thisME.RecordPartnerResponseErrors(publisher, partner, err) + } +} + +// RecordMisConfigurationErrorStats across all engines +func (me *MultiMetricsEngine) RecordPartnerConfigErrors(publisher, profile, partner string, errcode int) { + for _, thisME := range *me { + thisME.RecordPartnerConfigErrors(publisher, profile, partner, errcode) + } +} + +// RecordPublisherProfileRequests across all engines +func (me *MultiMetricsEngine) RecordPublisherProfileRequests(publisher, profile string) { + for _, thisME := range *me { + thisME.RecordPublisherProfileRequests(publisher, profile) + } +} + +// RecordPublisherInvalidProfileImpressions across all engines +func (me *MultiMetricsEngine) RecordPublisherInvalidProfileImpressions(publisher, profileID string, impCount int) { + for _, thisME := range *me { + thisME.RecordPublisherInvalidProfileImpressions(publisher, profileID, impCount) + } +} + +// RecordNobidErrPrebidServerRequests across all engines +func (me *MultiMetricsEngine) RecordNobidErrPrebidServerRequests(publisher string, nbr int) { + for _, thisME := range *me { + thisME.RecordNobidErrPrebidServerRequests(publisher, nbr) + } +} + +// RecordNobidErrPrebidServerResponse across all engines +func (me *MultiMetricsEngine) RecordNobidErrPrebidServerResponse(publisher string) { + for _, thisME := range *me { + thisME.RecordNobidErrPrebidServerResponse(publisher) + } +} + +// RecordInvalidCreativeStats across all engines +func (me *MultiMetricsEngine) RecordInvalidCreativeStats(publisher, partner string) { + for _, thisME := range *me { + thisME.RecordInvalidCreativeStats(publisher, partner) + } +} + +// RecordInvalidCreativeStats across all engines +func (me *MultiMetricsEngine) RecordPlatformPublisherPartnerReqStats(platform, publisher, partner string) { + for _, thisME := range *me { + thisME.RecordPlatformPublisherPartnerReqStats(platform, publisher, partner) + } +} + +// RecordInvalidCreativeStats across all engines +func (me *MultiMetricsEngine) RecordPlatformPublisherPartnerResponseStats(platform, publisher, partner string) { + for _, thisME := range *me { + thisME.RecordPlatformPublisherPartnerResponseStats(platform, publisher, partner) + } +} + +// RecordPublisherResponseEncodingErrorStats across all engines +func (me *MultiMetricsEngine) RecordPublisherResponseEncodingErrorStats(publisher string) { + for _, thisME := range *me { + thisME.RecordPublisherResponseEncodingErrorStats(publisher) + } +} + +// RecordPartnerResponseTimeStats across all engines +func (me *MultiMetricsEngine) RecordPartnerResponseTimeStats(publisher, profileID string, responseTime int) { + for _, thisME := range *me { + thisME.RecordPartnerResponseTimeStats(publisher, profileID, responseTime) + } +} + +// RecordPublisherResponseTimeStats across all engines +func (me *MultiMetricsEngine) RecordPublisherResponseTimeStats(publisher string, responseTime int) { + for _, thisME := range *me { + thisME.RecordPublisherResponseTimeStats(publisher, responseTime) + } +} + +// RecordPublisherWrapperLoggerFailure across all engines +func (me *MultiMetricsEngine) RecordPublisherWrapperLoggerFailure(publisher, profileID, versionID string) { + for _, thisME := range *me { + thisME.RecordPublisherWrapperLoggerFailure(publisher, profileID, versionID) + } +} + +// RecordCacheErrorRequests across all engines +func (me *MultiMetricsEngine) RecordCacheErrorRequests(endpoint, publisher, profileID string) { + for _, thisME := range *me { + thisME.RecordCacheErrorRequests(endpoint, publisher, profileID) + } +} + +// RecordPublisherInvalidProfileRequests across all engines +func (me *MultiMetricsEngine) RecordPublisherInvalidProfileRequests(endpoint, publisher, profileID string) { + for _, thisME := range *me { + thisME.RecordPublisherInvalidProfileRequests(endpoint, publisher, profileID) + } +} + +// RecordBadRequests across all engines +func (me *MultiMetricsEngine) RecordBadRequests(endpoint string, errorCode int) { + for _, thisME := range *me { + thisME.RecordBadRequests(endpoint, errorCode) + } +} + +// RecordPrebidTimeoutRequests across all engines +func (me *MultiMetricsEngine) RecordPrebidTimeoutRequests(publisher, profileID string) { + for _, thisME := range *me { + thisME.RecordPrebidTimeoutRequests(publisher, profileID) + } +} + +// RecordSSTimeoutRequests across all engines +func (me *MultiMetricsEngine) RecordSSTimeoutRequests(publisher, profileID string) { + for _, thisME := range *me { + thisME.RecordSSTimeoutRequests(publisher, profileID) + } +} + +// RecordUidsCookieNotPresentErrorStats across all engines +func (me *MultiMetricsEngine) RecordUidsCookieNotPresentErrorStats(publisher, profileID string) { + for _, thisME := range *me { + thisME.RecordUidsCookieNotPresentErrorStats(publisher, profileID) + } +} + +// RecordVideoInstlImpsStats across all engines +func (me *MultiMetricsEngine) RecordVideoInstlImpsStats(publisher, profileID string) { + for _, thisME := range *me { + thisME.RecordVideoInstlImpsStats(publisher, profileID) + } +} + +// RecordImpDisabledViaConfigStats across all engines +func (me *MultiMetricsEngine) RecordImpDisabledViaConfigStats(impType, publisher, profileID string) { + for _, thisME := range *me { + thisME.RecordImpDisabledViaConfigStats(impType, publisher, profileID) + } +} + +// RecordPreProcessingTimeStats across all engines +func (me *MultiMetricsEngine) RecordPreProcessingTimeStats(publisher string, processingTime int) { + for _, thisME := range *me { + thisME.RecordPreProcessingTimeStats(publisher, processingTime) + } +} + +// RecordStatsKeyCTVPrebidFailedImpression across all engines +func (me *MultiMetricsEngine) RecordStatsKeyCTVPrebidFailedImpression(errorcode int, publisher string, profile string) { + for _, thisME := range *me { + thisME.RecordStatsKeyCTVPrebidFailedImpression(errorcode, publisher, profile) + } +} + +// RecordCTVRequests across all engines +func (me *MultiMetricsEngine) RecordCTVRequests(endpoint, platform string) { + for _, thisME := range *me { + thisME.RecordCTVRequests(endpoint, platform) + } +} + +// RecordPublisherRequests across all engines +func (me *MultiMetricsEngine) RecordPublisherRequests(endpoint, publisher, platform string) { + for _, thisME := range *me { + thisME.RecordPublisherRequests(endpoint, publisher, platform) + } +} + +// RecordCTVHTTPMethodRequests across all engines +func (me *MultiMetricsEngine) RecordCTVHTTPMethodRequests(endpoint, publisher, method string) { + for _, thisME := range *me { + thisME.RecordCTVHTTPMethodRequests(endpoint, publisher, method) + } +} + +// RecordCTVInvalidReasonCount across all engines +func (me *MultiMetricsEngine) RecordCTVInvalidReasonCount(errorCode int, publisher string) { + for _, thisME := range *me { + thisME.RecordCTVInvalidReasonCount(errorCode, publisher) + } +} + +// RecordCTVReqImpsWithDbConfigCount across all engines +func (me *MultiMetricsEngine) RecordCTVReqImpsWithDbConfigCount(publisher string) { + for _, thisME := range *me { + thisME.RecordCTVReqImpsWithDbConfigCount(publisher) + } +} + +// RecordCTVReqImpsWithReqConfigCount across all engines +func (me *MultiMetricsEngine) RecordCTVReqImpsWithReqConfigCount(publisher string) { + for _, thisME := range *me { + thisME.RecordCTVReqImpsWithReqConfigCount(publisher) + } +} + +// RecordAdPodGeneratedImpressionsCount across all engines +func (me *MultiMetricsEngine) RecordAdPodGeneratedImpressionsCount(impCount int, publisher string) { + for _, thisME := range *me { + thisME.RecordAdPodGeneratedImpressionsCount(impCount, publisher) + } +} + +// RecordRequestAdPodGeneratedImpressionsCount across all engines +func (me *MultiMetricsEngine) RecordRequestAdPodGeneratedImpressionsCount(impCount int, publisher string) { + for _, thisME := range *me { + thisME.RecordRequestAdPodGeneratedImpressionsCount(impCount, publisher) + } +} + +// RecordAdPodImpressionYield across all engines +func (me *MultiMetricsEngine) RecordAdPodImpressionYield(maxDuration int, minDuration int, publisher string) { + for _, thisME := range *me { + thisME.RecordAdPodImpressionYield(maxDuration, minDuration, publisher) + } +} + +// RecordCTVReqCountWithAdPod across all engines +func (me *MultiMetricsEngine) RecordCTVReqCountWithAdPod(publisher, profile string) { + for _, thisME := range *me { + thisME.RecordCTVReqCountWithAdPod(publisher, profile) + } +} + +// RecordReqImpsWithContentCount across all engines +func (me *MultiMetricsEngine) RecordReqImpsWithContentCount(publisher, contentType string) { + for _, thisME := range *me { + thisME.RecordReqImpsWithContentCount(publisher, contentType) + } +} + +// RecordPBSAuctionRequestsStats across all engines +func (me *MultiMetricsEngine) RecordPBSAuctionRequestsStats() { + for _, thisME := range *me { + thisME.RecordPBSAuctionRequestsStats() + } +} + +// RecordInjectTrackerErrorCount across all engines +func (me *MultiMetricsEngine) RecordInjectTrackerErrorCount(adformat, publisher, partner string) { + for _, thisME := range *me { + thisME.RecordInjectTrackerErrorCount(adformat, publisher, partner) + } +} + +// RecordBidResponseByDealCountInPBS across all engines +func (me *MultiMetricsEngine) RecordBidResponseByDealCountInPBS(publisher, profile, aliasBidder, dealId string) { + for _, thisME := range *me { + thisME.RecordBidResponseByDealCountInPBS(publisher, profile, aliasBidder, dealId) + } +} + +// RecordBidResponseByDealCountInHB across all engines +func (me *MultiMetricsEngine) RecordBidResponseByDealCountInHB(publisher, profile, aliasBidder, dealId string) { + for _, thisME := range *me { + thisME.RecordBidResponseByDealCountInHB(publisher, profile, aliasBidder, dealId) + } +} + +// RecordPartnerTimeoutInPBS across all engines +func (me *MultiMetricsEngine) RecordPartnerTimeoutInPBS(publisher, profile, aliasBidder string) { + for _, thisME := range *me { + thisME.RecordPartnerTimeoutInPBS(publisher, profile, aliasBidder) + } +} + +// RecordVideoImpDisabledViaConnTypeStats across all engines +func (me *MultiMetricsEngine) RecordVideoImpDisabledViaConnTypeStats(publisher, profile string) { + for _, thisME := range *me { + thisME.RecordVideoImpDisabledViaConnTypeStats(publisher, profile) + } +} + +// RecordGetProfileDataTime across all engines +func (me *MultiMetricsEngine) RecordGetProfileDataTime(requestType, profileid string, getTime time.Duration) { + for _, thisME := range *me { + thisME.RecordGetProfileDataTime(requestType, profileid, getTime) + } +} + +// RecordDBQueryFailure across all engines +func (me *MultiMetricsEngine) RecordDBQueryFailure(queryType, publisher, profile string) { + for _, thisME := range *me { + thisME.RecordDBQueryFailure(queryType, publisher, profile) + } +} + +// Shutdown across all engines +func (me *MultiMetricsEngine) Shutdown() { + for _, thisME := range *me { + thisME.Shutdown() + } +} + +// RecordRequest log openwrap request type +func (me *MultiMetricsEngine) RecordRequest(labels metrics.Labels) { + for _, thisME := range *me { + thisME.RecordRequest(labels) + } +} + +// RecordLurlSent log lurl status +func (me *MultiMetricsEngine) RecordLurlSent(labels metrics.LurlStatusLabels) { + for _, thisME := range *me { + thisME.RecordLurlSent(labels) + } +} + +// RecordLurlBatchSent log lurl batch status +func (me *MultiMetricsEngine) RecordLurlBatchSent(labels metrics.LurlBatchStatusLabels) { + for _, thisME := range *me { + thisME.RecordLurlBatchSent(labels) + } +} + +// RecordBids record ow bids +func (me *MultiMetricsEngine) RecordBids(pubid, profileid, biddder, deal string) { + for _, thisME := range *me { + thisME.RecordBids(pubid, profileid, biddder, deal) + } +} + +// RecordPartnerTimeoutRequests log request partner request timeout +func (me *MultiMetricsEngine) RecordPartnerTimeoutRequests(pubid, profileid, bidder string) { + for _, thisME := range *me { + thisME.RecordPartnerTimeoutRequests(pubid, profileid, bidder) + } +} + +// RecordCtvUaAccuracy log ctv UA accuracy +func (me *MultiMetricsEngine) RecordCtvUaAccuracy(pubId, status string) { + for _, thisME := range *me { + thisME.RecordCtvUaAccuracy(pubId, status) + } +} + +// RecordSendLoggerDataTime across all engines +func (me *MultiMetricsEngine) RecordSendLoggerDataTime(endpoint, profile string, sendTime time.Duration) { + for _, thisME := range *me { + thisME.RecordSendLoggerDataTime(endpoint, profile, sendTime) + } +} + +// RecordRequestTime record ow request time +func (me *MultiMetricsEngine) RecordRequestTime(requestType string, requestTime time.Duration) { + for _, thisME := range *me { + thisME.RecordRequestTime(requestType, requestTime) + } +} + +// RecordOWServerPanic record OW panics +func (me *MultiMetricsEngine) RecordOWServerPanic(endpoint, methodName, nodeName, podName string) { + for _, thisME := range *me { + thisME.RecordOWServerPanic(endpoint, methodName, nodeName, podName) + } +} diff --git a/modules/pubmatic/openwrap/metrics/config/metrics_test.go b/modules/pubmatic/openwrap/metrics/config/metrics_test.go new file mode 100644 index 00000000000..81149a17c9d --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/config/metrics_test.go @@ -0,0 +1,282 @@ +package config + +import ( + "fmt" + "testing" + "time" + + "github.com/golang/mock/gomock" + cfg "github.com/prebid/prebid-server/v2/config" + metrics_cfg "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + mock "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/stats" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" +) + +func TestNewMetricsEngine(t *testing.T) { + + type args struct { + owConfig *config.Config + metricsRegistry metrics_cfg.MetricsRegistry + metricsCfg *cfg.Metrics + } + type want struct { + expectNilEngine bool + err error + metricsEngineCnt int + } + testCases := []struct { + name string + args args + want want + }{ + { + name: "valid_configurations", + args: args{ + owConfig: &config.Config{ + Stats: stats.Stats{ + Endpoint: "http://example.com", + UseHostName: true, + }, + }, + metricsRegistry: metrics_cfg.MetricsRegistry{ + metrics_cfg.PrometheusRegistry: prometheus.NewRegistry(), + }, + metricsCfg: &cfg.Metrics{ + Prometheus: cfg.PrometheusMetrics{ + Port: 14404, + Namespace: "ow", + Subsystem: "pbs", + TimeoutMillisRaw: 10, + }, + }, + }, + want: want{ + expectNilEngine: false, + err: nil, + metricsEngineCnt: 2, + }, + }, + { + name: "empty_stat_config_and_nil_metrics_config", + args: args{ + owConfig: &config.Config{ + Stats: stats.Stats{ + Endpoint: "", + }, + }, + metricsRegistry: metrics_cfg.MetricsRegistry{ + metrics_cfg.PrometheusRegistry: prometheus.NewRegistry(), + }, + metricsCfg: nil, + }, + want: want{ + expectNilEngine: true, + err: fmt.Errorf("metric-engine is not configured"), + }, + }, + { + name: "empty_stat_config_and_nil_metrics_registry", + args: args{ + owConfig: &config.Config{ + Stats: stats.Stats{ + Endpoint: "", + }, + }, + metricsRegistry: metrics_cfg.MetricsRegistry{ + metrics_cfg.PrometheusRegistry: nil, + }, + metricsCfg: &cfg.Metrics{ + Prometheus: cfg.PrometheusMetrics{}, + }, + }, + want: want{ + expectNilEngine: true, + err: fmt.Errorf("metric-engine is not configured"), + }, + }, + { + name: "empty_stat_and_valid_metrics_cfg_and_registry", + args: args{ + owConfig: &config.Config{ + Stats: stats.Stats{ + Endpoint: "", + }, + }, + metricsRegistry: metrics_cfg.MetricsRegistry{ + metrics_cfg.PrometheusRegistry: prometheus.NewRegistry(), + }, + metricsCfg: &cfg.Metrics{ + Prometheus: cfg.PrometheusMetrics{}, + }, + }, + want: want{ + expectNilEngine: false, + err: nil, + metricsEngineCnt: 1, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actualOutput, actualError := NewMetricsEngine(tc.args.owConfig, tc.args.metricsCfg, tc.args.metricsRegistry) + assert.Equal(t, tc.want.expectNilEngine, actualOutput == nil) + assert.Equal(t, tc.want.err, actualError) + assert.Equal(t, tc.want.metricsEngineCnt, len(actualOutput)) + }) + } +} + +func TestRecordFunctionForMultiMetricsEngine(t *testing.T) { + ctrl := gomock.NewController(t) + mockEngine := mock.NewMockMetricsEngine(ctrl) + defer ctrl.Finish() + + // set the variables + publisher := "5890" + profile := "123" + partner := "pubmatic" + impCount := 1 + platform := "video" + responseTime := 1 + endpoint := "in-app" + versionID := "1" + errorCode := 10 + processingTime := 10 + method := "GET" + maxDuration := 20 + minDuration := 10 + aliasBidder := "pubmatic-2" + adFormat := "banner" + dealId := "pubdeal" + host := "sv3:xyz1234" + getTime, sendTime := 300*time.Millisecond, 300*time.Millisecond + queryType := models.AdunitConfigQuery + + // set the expectations + mockEngine.EXPECT().RecordOpenWrapServerPanicStats(host, method) + mockEngine.EXPECT().RecordPublisherPartnerNoCookieStats(publisher, partner) + mockEngine.EXPECT().RecordPartnerResponseErrors(publisher, partner, models.PartnerErrTimeout) + mockEngine.EXPECT().RecordPartnerConfigErrors(publisher, profile, partner, models.PartnerErrSlotNotMapped) + + mockEngine.EXPECT().RecordPublisherProfileRequests(publisher, profile) + mockEngine.EXPECT().RecordPublisherInvalidProfileImpressions(publisher, profile, impCount) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests(publisher, nbr.AllPartnerThrottled) + mockEngine.EXPECT().RecordNobidErrPrebidServerResponse(publisher) + mockEngine.EXPECT().RecordInvalidCreativeStats(publisher, partner) + mockEngine.EXPECT().RecordPlatformPublisherPartnerReqStats(platform, publisher, partner) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats(platform, publisher, partner) + mockEngine.EXPECT().RecordPublisherResponseEncodingErrorStats(publisher) + mockEngine.EXPECT().RecordPartnerResponseTimeStats(publisher, partner, responseTime) + mockEngine.EXPECT().RecordPublisherResponseTimeStats(publisher, responseTime) + mockEngine.EXPECT().RecordPublisherWrapperLoggerFailure(publisher, profile, versionID) + mockEngine.EXPECT().RecordCacheErrorRequests(endpoint, publisher, profile) + mockEngine.EXPECT().RecordPublisherInvalidProfileRequests(endpoint, publisher, profile) + mockEngine.EXPECT().RecordBadRequests(endpoint, errorCode) + mockEngine.EXPECT().RecordPrebidTimeoutRequests(publisher, profile) + mockEngine.EXPECT().RecordSSTimeoutRequests(publisher, profile) + mockEngine.EXPECT().RecordUidsCookieNotPresentErrorStats(publisher, profile) + mockEngine.EXPECT().RecordVideoInstlImpsStats(publisher, profile) + mockEngine.EXPECT().RecordImpDisabledViaConfigStats(adFormat, publisher, profile) + mockEngine.EXPECT().RecordPreProcessingTimeStats(publisher, processingTime) + mockEngine.EXPECT().RecordStatsKeyCTVPrebidFailedImpression(errorCode, publisher, profile) + mockEngine.EXPECT().RecordCTVRequests(endpoint, platform) + mockEngine.EXPECT().RecordPublisherRequests(endpoint, publisher, platform) + mockEngine.EXPECT().RecordCTVHTTPMethodRequests(endpoint, publisher, method) + mockEngine.EXPECT().RecordCTVInvalidReasonCount(errorCode, publisher) + mockEngine.EXPECT().RecordCTVReqImpsWithDbConfigCount(publisher) + mockEngine.EXPECT().RecordCTVReqImpsWithReqConfigCount(publisher) + mockEngine.EXPECT().RecordAdPodGeneratedImpressionsCount(impCount, publisher) + mockEngine.EXPECT().RecordRequestAdPodGeneratedImpressionsCount(impCount, publisher) + mockEngine.EXPECT().RecordReqImpsWithContentCount(publisher, models.ContentTypeSite) + mockEngine.EXPECT().RecordAdPodImpressionYield(maxDuration, minDuration, publisher) + mockEngine.EXPECT().RecordCTVReqCountWithAdPod(publisher, profile) + mockEngine.EXPECT().RecordPBSAuctionRequestsStats() + mockEngine.EXPECT().RecordInjectTrackerErrorCount(adFormat, publisher, partner) + mockEngine.EXPECT().RecordBidResponseByDealCountInPBS(publisher, profile, aliasBidder, dealId) + mockEngine.EXPECT().RecordBidResponseByDealCountInHB(publisher, profile, aliasBidder, dealId) + mockEngine.EXPECT().RecordPartnerTimeoutInPBS(publisher, profile, aliasBidder) + mockEngine.EXPECT().RecordVideoImpDisabledViaConnTypeStats(publisher, profile) + mockEngine.EXPECT().RecordGetProfileDataTime(endpoint, profile, getTime) + mockEngine.EXPECT().RecordSendLoggerDataTime(endpoint, profile, sendTime) + mockEngine.EXPECT().RecordDBQueryFailure(queryType, publisher, profile) + mockEngine.EXPECT().Shutdown() + + mockEngine.EXPECT().RecordRequest(metrics.Labels{RType: "video", RequestStatus: "success"}) + mockEngine.EXPECT().RecordLurlSent(metrics.LurlStatusLabels{PublisherID: "pubid", Partner: "p", Status: "success"}) + mockEngine.EXPECT().RecordLurlBatchSent(metrics.LurlBatchStatusLabels{Status: "success"}) + mockEngine.EXPECT().RecordBids("pubid", "profileid", "bidder", "deal") + mockEngine.EXPECT().RecordPartnerTimeoutRequests("pubid", "profileid", "bidder") + mockEngine.EXPECT().RecordCtvUaAccuracy("pubId", "status") + mockEngine.EXPECT().RecordSendLoggerDataTime("requestType", "profileid", time.Second) + mockEngine.EXPECT().RecordRequestTime("requestType", time.Second) + mockEngine.EXPECT().RecordOWServerPanic("endpoint", "methodName", "nodeName", "podName") + + // create the multi-metric engine + multiMetricEngine := MultiMetricsEngine{} + multiMetricEngine = append(multiMetricEngine, mockEngine) + + // call the functions + multiMetricEngine.RecordOpenWrapServerPanicStats(host, method) + multiMetricEngine.RecordPublisherPartnerNoCookieStats(publisher, partner) + multiMetricEngine.RecordPartnerResponseErrors(publisher, partner, models.PartnerErrTimeout) + multiMetricEngine.RecordPartnerConfigErrors(publisher, profile, partner, models.PartnerErrSlotNotMapped) + multiMetricEngine.RecordPublisherProfileRequests(publisher, profile) + multiMetricEngine.RecordPublisherInvalidProfileImpressions(publisher, profile, impCount) + multiMetricEngine.RecordNobidErrPrebidServerRequests(publisher, nbr.AllPartnerThrottled) + multiMetricEngine.RecordNobidErrPrebidServerResponse(publisher) + multiMetricEngine.RecordInvalidCreativeStats(publisher, partner) + multiMetricEngine.RecordPlatformPublisherPartnerReqStats(platform, publisher, partner) + multiMetricEngine.RecordPlatformPublisherPartnerResponseStats(platform, publisher, partner) + multiMetricEngine.RecordPublisherResponseEncodingErrorStats(publisher) + multiMetricEngine.RecordPartnerResponseTimeStats(publisher, partner, responseTime) + multiMetricEngine.RecordPublisherResponseTimeStats(publisher, responseTime) + multiMetricEngine.RecordPublisherWrapperLoggerFailure(publisher, profile, versionID) + multiMetricEngine.RecordCacheErrorRequests(endpoint, publisher, profile) + multiMetricEngine.RecordPublisherInvalidProfileRequests(endpoint, publisher, profile) + multiMetricEngine.RecordBadRequests(endpoint, errorCode) + multiMetricEngine.RecordPrebidTimeoutRequests(publisher, profile) + multiMetricEngine.RecordSSTimeoutRequests(publisher, profile) + multiMetricEngine.RecordUidsCookieNotPresentErrorStats(publisher, profile) + multiMetricEngine.RecordVideoInstlImpsStats(publisher, profile) + multiMetricEngine.RecordImpDisabledViaConfigStats(adFormat, publisher, profile) + multiMetricEngine.RecordPreProcessingTimeStats(publisher, processingTime) + multiMetricEngine.RecordStatsKeyCTVPrebidFailedImpression(errorCode, publisher, profile) + multiMetricEngine.RecordCTVRequests(endpoint, platform) + multiMetricEngine.RecordPublisherRequests(endpoint, publisher, platform) + multiMetricEngine.RecordCTVHTTPMethodRequests(endpoint, publisher, method) + multiMetricEngine.RecordCTVInvalidReasonCount(errorCode, publisher) + multiMetricEngine.RecordCTVReqImpsWithDbConfigCount(publisher) + multiMetricEngine.RecordCTVReqImpsWithReqConfigCount(publisher) + multiMetricEngine.RecordAdPodGeneratedImpressionsCount(impCount, publisher) + multiMetricEngine.RecordRequestAdPodGeneratedImpressionsCount(impCount, publisher) + multiMetricEngine.RecordReqImpsWithContentCount(publisher, models.ContentTypeSite) + multiMetricEngine.RecordAdPodImpressionYield(maxDuration, minDuration, publisher) + multiMetricEngine.RecordCTVReqCountWithAdPod(publisher, profile) + multiMetricEngine.RecordPBSAuctionRequestsStats() + multiMetricEngine.RecordInjectTrackerErrorCount(adFormat, publisher, partner) + multiMetricEngine.RecordBidResponseByDealCountInPBS(publisher, profile, aliasBidder, dealId) + multiMetricEngine.RecordBidResponseByDealCountInHB(publisher, profile, aliasBidder, dealId) + multiMetricEngine.RecordPartnerTimeoutInPBS(publisher, profile, aliasBidder) + multiMetricEngine.RecordVideoImpDisabledViaConnTypeStats(publisher, profile) + multiMetricEngine.RecordGetProfileDataTime(endpoint, profile, getTime) + multiMetricEngine.RecordSendLoggerDataTime(endpoint, profile, sendTime) + multiMetricEngine.RecordDBQueryFailure(queryType, publisher, profile) + multiMetricEngine.Shutdown() + + multiMetricEngine.RecordRequest(metrics.Labels{RType: "video", RequestStatus: "success"}) + multiMetricEngine.RecordLurlSent(metrics.LurlStatusLabels{PublisherID: "pubid", Partner: "p", Status: "success"}) + multiMetricEngine.RecordLurlBatchSent(metrics.LurlBatchStatusLabels{Status: "success"}) + multiMetricEngine.RecordBids("pubid", "profileid", "bidder", "deal") + multiMetricEngine.RecordPartnerTimeoutRequests("pubid", "profileid", "bidder") + multiMetricEngine.RecordCtvUaAccuracy("pubId", "status") + multiMetricEngine.RecordSendLoggerDataTime("requestType", "profileid", time.Second) + multiMetricEngine.RecordRequestTime("requestType", time.Second) + multiMetricEngine.RecordOWServerPanic("endpoint", "methodName", "nodeName", "podName") +} diff --git a/modules/pubmatic/openwrap/metrics/metrics.go b/modules/pubmatic/openwrap/metrics/metrics.go new file mode 100644 index 00000000000..3d00bf73219 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/metrics.go @@ -0,0 +1,75 @@ +package metrics + +import "time" + +// MetricsEngine is a generic interface to record PBS metrics into the desired backend +type MetricsEngine interface { + RecordOpenWrapServerPanicStats(hostName, method string) + RecordPublisherPartnerNoCookieStats(publisher, partner string) + RecordPartnerResponseErrors(publisherID, partner, err string) + RecordPartnerConfigErrors(publisherID, profileID, partner string, errcode int) + RecordPublisherProfileRequests(publisher, profileID string) + RecordPublisherInvalidProfileImpressions(publisher, profileID string, impCount int) + RecordNobidErrPrebidServerRequests(publisher string, nbr int) + RecordNobidErrPrebidServerResponse(publisher string) + RecordPlatformPublisherPartnerReqStats(platform, publisher, partner string) + RecordPlatformPublisherPartnerResponseStats(platform, publisher, partner string) + RecordPartnerResponseTimeStats(publisher, partner string, responseTime int) + RecordPublisherResponseTimeStats(publisher string, responseTimeMs int) + RecordPublisherWrapperLoggerFailure(publisher, profileID, versionID string) + RecordPublisherInvalidProfileRequests(endpoint, publisher, profileID string) + RecordBadRequests(endpoint string, errorCode int) + RecordUidsCookieNotPresentErrorStats(publisher, profileID string) + RecordVideoInstlImpsStats(publisher, profileID string) + RecordImpDisabledViaConfigStats(impType, publisher, profileID string) + RecordPublisherRequests(endpoint string, publisher string, platform string) + RecordReqImpsWithContentCount(publisher, contentType string) + RecordInjectTrackerErrorCount(adformat, publisher, partner string) + + // not-captured in openwrap module, dont provide enough insights + RecordPBSAuctionRequestsStats() + RecordInvalidCreativeStats(publisher, partner string) + + // not implemented in openwrap module yet + RecordCacheErrorRequests(endpoint string, publisher string, profileID string) + RecordPublisherResponseEncodingErrorStats(publisher string) + RecordVideoImpDisabledViaConnTypeStats(publisher, profileID string) + + // not applicable for openwrap module + RecordSSTimeoutRequests(publisher, profileID string) + RecordPartnerTimeoutInPBS(publisher, profile, aliasBidder string) + RecordPreProcessingTimeStats(publisher string, processingTime int) + + // CTV specific metrics (not implemented in openwrap module yet) + RecordStatsKeyCTVPrebidFailedImpression(errorcode int, publisher string, profile string) + RecordCTVRequests(endpoint string, platform string) + RecordCTVHTTPMethodRequests(endpoint string, publisher string, method string) + RecordCTVInvalidReasonCount(errorCode int, publisher string) + RecordCTVReqImpsWithDbConfigCount(publisher string) + RecordCTVReqImpsWithReqConfigCount(publisher string) + RecordAdPodGeneratedImpressionsCount(impCount int, publisher string) + RecordRequestAdPodGeneratedImpressionsCount(impCount int, publisher string) + RecordAdPodImpressionYield(maxDuration int, minDuration int, publisher string) + RecordCTVReqCountWithAdPod(publisherID, profileID string) + + // stats-server specific metrics + RecordBidResponseByDealCountInPBS(publisher, profile, aliasBidder, dealId string) + RecordBidResponseByDealCountInHB(publisher, profile, aliasBidder, dealId string) + + RecordGetProfileDataTime(endpoint, profile string, getTime time.Duration) + RecordDBQueryFailure(queryType, publisher, profile string) + + Shutdown() + + // temporary sshb metrics + RecordRequest(labels Labels) // ignores adapter. only statusOk and statusErr fom status + RecordLurlSent(labels LurlStatusLabels) + RecordLurlBatchSent(labels LurlBatchStatusLabels) + RecordBids(pubid, profileid, biddder, deal string) + RecordPrebidTimeoutRequests(pubid, profileid string) + RecordPartnerTimeoutRequests(pubid, profileid, bidder string) + RecordCtvUaAccuracy(pubId, status string) + RecordSendLoggerDataTime(requestType, profileid string, sendTime time.Duration) + RecordRequestTime(requestType string, requestTime time.Duration) + RecordOWServerPanic(endpoint, methodName, nodeName, podName string) +} diff --git a/modules/pubmatic/openwrap/metrics/metrics_sshb.go b/modules/pubmatic/openwrap/metrics/metrics_sshb.go new file mode 100644 index 00000000000..048fe203c66 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/metrics_sshb.go @@ -0,0 +1,25 @@ +package metrics + +// Labels defines the labels that can be attached to the metrics. +type Labels struct { + RType RequestType + RequestStatus RequestStatus +} + +// RequestType : Request type enumeration +type RequestType string + +// RequestStatus : The request return status +type RequestStatus string + +// LurlStatusLabels defines labels applicable for LURL sent +type LurlStatusLabels struct { + PublisherID string + Partner string + Status string +} + +// LurlBatchStatusLabels defines labels applicable for LURL batche sent +type LurlBatchStatusLabels struct { + Status string +} diff --git a/modules/pubmatic/openwrap/metrics/mock/mock.go b/modules/pubmatic/openwrap/metrics/mock/mock.go new file mode 100644 index 00000000000..c6de5bf9829 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/mock/mock.go @@ -0,0 +1,684 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/PubMatic-OpenWrap/prebid-server/modules/pubmatic/openwrap/metrics (interfaces: MetricsEngine) + +// Package mock_metrics is a generated GoMock package. +package mock_metrics + +import ( + reflect "reflect" + time "time" + + gomock "github.com/golang/mock/gomock" + metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" +) + +// MockMetricsEngine is a mock of MetricsEngine interface. +type MockMetricsEngine struct { + ctrl *gomock.Controller + recorder *MockMetricsEngineMockRecorder +} + +// MockMetricsEngineMockRecorder is the mock recorder for MockMetricsEngine. +type MockMetricsEngineMockRecorder struct { + mock *MockMetricsEngine +} + +// NewMockMetricsEngine creates a new mock instance. +func NewMockMetricsEngine(ctrl *gomock.Controller) *MockMetricsEngine { + mock := &MockMetricsEngine{ctrl: ctrl} + mock.recorder = &MockMetricsEngineMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMetricsEngine) EXPECT() *MockMetricsEngineMockRecorder { + return m.recorder +} + +// RecordAdPodGeneratedImpressionsCount mocks base method. +func (m *MockMetricsEngine) RecordAdPodGeneratedImpressionsCount(arg0 int, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordAdPodGeneratedImpressionsCount", arg0, arg1) +} + +// RecordAdPodGeneratedImpressionsCount indicates an expected call of RecordAdPodGeneratedImpressionsCount. +func (mr *MockMetricsEngineMockRecorder) RecordAdPodGeneratedImpressionsCount(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordAdPodGeneratedImpressionsCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordAdPodGeneratedImpressionsCount), arg0, arg1) +} + +// RecordAdPodImpressionYield mocks base method. +func (m *MockMetricsEngine) RecordAdPodImpressionYield(arg0, arg1 int, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordAdPodImpressionYield", arg0, arg1, arg2) +} + +// RecordAdPodImpressionYield indicates an expected call of RecordAdPodImpressionYield. +func (mr *MockMetricsEngineMockRecorder) RecordAdPodImpressionYield(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordAdPodImpressionYield", reflect.TypeOf((*MockMetricsEngine)(nil).RecordAdPodImpressionYield), arg0, arg1, arg2) +} + +// RecordBadRequests mocks base method. +func (m *MockMetricsEngine) RecordBadRequests(arg0 string, arg1 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordBadRequests", arg0, arg1) +} + +// RecordBadRequests indicates an expected call of RecordBadRequests. +func (mr *MockMetricsEngineMockRecorder) RecordBadRequests(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBadRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBadRequests), arg0, arg1) +} + +// RecordBidResponseByDealCountInHB mocks base method. +func (m *MockMetricsEngine) RecordBidResponseByDealCountInHB(arg0, arg1, arg2, arg3 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordBidResponseByDealCountInHB", arg0, arg1, arg2, arg3) +} + +// RecordBidResponseByDealCountInHB indicates an expected call of RecordBidResponseByDealCountInHB. +func (mr *MockMetricsEngineMockRecorder) RecordBidResponseByDealCountInHB(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBidResponseByDealCountInHB", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBidResponseByDealCountInHB), arg0, arg1, arg2, arg3) +} + +// RecordBidResponseByDealCountInPBS mocks base method. +func (m *MockMetricsEngine) RecordBidResponseByDealCountInPBS(arg0, arg1, arg2, arg3 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordBidResponseByDealCountInPBS", arg0, arg1, arg2, arg3) +} + +// RecordBidResponseByDealCountInPBS indicates an expected call of RecordBidResponseByDealCountInPBS. +func (mr *MockMetricsEngineMockRecorder) RecordBidResponseByDealCountInPBS(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBidResponseByDealCountInPBS", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBidResponseByDealCountInPBS), arg0, arg1, arg2, arg3) +} + +// RecordBids mocks base method. +func (m *MockMetricsEngine) RecordBids(arg0, arg1, arg2, arg3 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordBids", arg0, arg1, arg2, arg3) +} + +// RecordBids indicates an expected call of RecordBids. +func (mr *MockMetricsEngineMockRecorder) RecordBids(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBids", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBids), arg0, arg1, arg2, arg3) +} + +// RecordCTVHTTPMethodRequests mocks base method. +func (m *MockMetricsEngine) RecordCTVHTTPMethodRequests(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordCTVHTTPMethodRequests", arg0, arg1, arg2) +} + +// RecordCTVHTTPMethodRequests indicates an expected call of RecordCTVHTTPMethodRequests. +func (mr *MockMetricsEngineMockRecorder) RecordCTVHTTPMethodRequests(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVHTTPMethodRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVHTTPMethodRequests), arg0, arg1, arg2) +} + +// RecordCTVInvalidReasonCount mocks base method. +func (m *MockMetricsEngine) RecordCTVInvalidReasonCount(arg0 int, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordCTVInvalidReasonCount", arg0, arg1) +} + +// RecordCTVInvalidReasonCount indicates an expected call of RecordCTVInvalidReasonCount. +func (mr *MockMetricsEngineMockRecorder) RecordCTVInvalidReasonCount(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVInvalidReasonCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVInvalidReasonCount), arg0, arg1) +} + +// RecordCTVReqCountWithAdPod mocks base method. +func (m *MockMetricsEngine) RecordCTVReqCountWithAdPod(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordCTVReqCountWithAdPod", arg0, arg1) +} + +// RecordCTVReqCountWithAdPod indicates an expected call of RecordCTVReqCountWithAdPod. +func (mr *MockMetricsEngineMockRecorder) RecordCTVReqCountWithAdPod(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVReqCountWithAdPod", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVReqCountWithAdPod), arg0, arg1) +} + +// RecordCTVReqImpsWithDbConfigCount mocks base method. +func (m *MockMetricsEngine) RecordCTVReqImpsWithDbConfigCount(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordCTVReqImpsWithDbConfigCount", arg0) +} + +// RecordCTVReqImpsWithDbConfigCount indicates an expected call of RecordCTVReqImpsWithDbConfigCount. +func (mr *MockMetricsEngineMockRecorder) RecordCTVReqImpsWithDbConfigCount(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVReqImpsWithDbConfigCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVReqImpsWithDbConfigCount), arg0) +} + +// RecordCTVReqImpsWithReqConfigCount mocks base method. +func (m *MockMetricsEngine) RecordCTVReqImpsWithReqConfigCount(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordCTVReqImpsWithReqConfigCount", arg0) +} + +// RecordCTVReqImpsWithReqConfigCount indicates an expected call of RecordCTVReqImpsWithReqConfigCount. +func (mr *MockMetricsEngineMockRecorder) RecordCTVReqImpsWithReqConfigCount(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVReqImpsWithReqConfigCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVReqImpsWithReqConfigCount), arg0) +} + +// RecordCTVRequests mocks base method. +func (m *MockMetricsEngine) RecordCTVRequests(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordCTVRequests", arg0, arg1) +} + +// RecordCTVRequests indicates an expected call of RecordCTVRequests. +func (mr *MockMetricsEngineMockRecorder) RecordCTVRequests(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVRequests), arg0, arg1) +} + +// RecordCacheErrorRequests mocks base method. +func (m *MockMetricsEngine) RecordCacheErrorRequests(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordCacheErrorRequests", arg0, arg1, arg2) +} + +// RecordCacheErrorRequests indicates an expected call of RecordCacheErrorRequests. +func (mr *MockMetricsEngineMockRecorder) RecordCacheErrorRequests(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCacheErrorRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCacheErrorRequests), arg0, arg1, arg2) +} + +// RecordCtvUaAccuracy mocks base method. +func (m *MockMetricsEngine) RecordCtvUaAccuracy(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordCtvUaAccuracy", arg0, arg1) +} + +// RecordCtvUaAccuracy indicates an expected call of RecordCtvUaAccuracy. +func (mr *MockMetricsEngineMockRecorder) RecordCtvUaAccuracy(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCtvUaAccuracy", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCtvUaAccuracy), arg0, arg1) +} + +// RecordDBQueryFailure mocks base method. +func (m *MockMetricsEngine) RecordDBQueryFailure(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordDBQueryFailure", arg0, arg1, arg2) +} + +// RecordDBQueryFailure indicates an expected call of RecordDBQueryFailure. +func (mr *MockMetricsEngineMockRecorder) RecordDBQueryFailure(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordDBQueryFailure", reflect.TypeOf((*MockMetricsEngine)(nil).RecordDBQueryFailure), arg0, arg1, arg2) +} + +// RecordGetProfileDataTime mocks base method. +func (m *MockMetricsEngine) RecordGetProfileDataTime(arg0, arg1 string, arg2 time.Duration) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordGetProfileDataTime", arg0, arg1, arg2) +} + +// RecordGetProfileDataTime indicates an expected call of RecordGetProfileDataTime. +func (mr *MockMetricsEngineMockRecorder) RecordGetProfileDataTime(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordGetProfileDataTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordGetProfileDataTime), arg0, arg1, arg2) +} + +// RecordImpDisabledViaConfigStats mocks base method. +func (m *MockMetricsEngine) RecordImpDisabledViaConfigStats(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordImpDisabledViaConfigStats", arg0, arg1, arg2) +} + +// RecordImpDisabledViaConfigStats indicates an expected call of RecordImpDisabledViaConfigStats. +func (mr *MockMetricsEngineMockRecorder) RecordImpDisabledViaConfigStats(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordImpDisabledViaConfigStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordImpDisabledViaConfigStats), arg0, arg1, arg2) +} + +// RecordInjectTrackerErrorCount mocks base method. +func (m *MockMetricsEngine) RecordInjectTrackerErrorCount(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordInjectTrackerErrorCount", arg0, arg1, arg2) +} + +// RecordInjectTrackerErrorCount indicates an expected call of RecordInjectTrackerErrorCount. +func (mr *MockMetricsEngineMockRecorder) RecordInjectTrackerErrorCount(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordInjectTrackerErrorCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordInjectTrackerErrorCount), arg0, arg1, arg2) +} + +// RecordInvalidCreativeStats mocks base method. +func (m *MockMetricsEngine) RecordInvalidCreativeStats(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordInvalidCreativeStats", arg0, arg1) +} + +// RecordInvalidCreativeStats indicates an expected call of RecordInvalidCreativeStats. +func (mr *MockMetricsEngineMockRecorder) RecordInvalidCreativeStats(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordInvalidCreativeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordInvalidCreativeStats), arg0, arg1) +} + +// RecordLurlBatchSent mocks base method. +func (m *MockMetricsEngine) RecordLurlBatchSent(arg0 metrics.LurlBatchStatusLabels) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordLurlBatchSent", arg0) +} + +// RecordLurlBatchSent indicates an expected call of RecordLurlBatchSent. +func (mr *MockMetricsEngineMockRecorder) RecordLurlBatchSent(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordLurlBatchSent", reflect.TypeOf((*MockMetricsEngine)(nil).RecordLurlBatchSent), arg0) +} + +// RecordLurlSent mocks base method. +func (m *MockMetricsEngine) RecordLurlSent(arg0 metrics.LurlStatusLabels) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordLurlSent", arg0) +} + +// RecordLurlSent indicates an expected call of RecordLurlSent. +func (mr *MockMetricsEngineMockRecorder) RecordLurlSent(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordLurlSent", reflect.TypeOf((*MockMetricsEngine)(nil).RecordLurlSent), arg0) +} + +// RecordNobidErrPrebidServerRequests mocks base method. +func (m *MockMetricsEngine) RecordNobidErrPrebidServerRequests(arg0 string, arg1 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordNobidErrPrebidServerRequests", arg0, arg1) +} + +// RecordNobidErrPrebidServerRequests indicates an expected call of RecordNobidErrPrebidServerRequests. +func (mr *MockMetricsEngineMockRecorder) RecordNobidErrPrebidServerRequests(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordNobidErrPrebidServerRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordNobidErrPrebidServerRequests), arg0, arg1) +} + +// RecordNobidErrPrebidServerResponse mocks base method. +func (m *MockMetricsEngine) RecordNobidErrPrebidServerResponse(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordNobidErrPrebidServerResponse", arg0) +} + +// RecordNobidErrPrebidServerResponse indicates an expected call of RecordNobidErrPrebidServerResponse. +func (mr *MockMetricsEngineMockRecorder) RecordNobidErrPrebidServerResponse(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordNobidErrPrebidServerResponse", reflect.TypeOf((*MockMetricsEngine)(nil).RecordNobidErrPrebidServerResponse), arg0) +} + +// RecordOWServerPanic mocks base method. +func (m *MockMetricsEngine) RecordOWServerPanic(arg0, arg1, arg2, arg3 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordOWServerPanic", arg0, arg1, arg2, arg3) +} + +// RecordOWServerPanic indicates an expected call of RecordOWServerPanic. +func (mr *MockMetricsEngineMockRecorder) RecordOWServerPanic(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordOWServerPanic", reflect.TypeOf((*MockMetricsEngine)(nil).RecordOWServerPanic), arg0, arg1, arg2, arg3) +} + +// RecordOpenWrapServerPanicStats mocks base method. +func (m *MockMetricsEngine) RecordOpenWrapServerPanicStats(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordOpenWrapServerPanicStats", arg0, arg1) +} + +// RecordOpenWrapServerPanicStats indicates an expected call of RecordOpenWrapServerPanicStats. +func (mr *MockMetricsEngineMockRecorder) RecordOpenWrapServerPanicStats(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordOpenWrapServerPanicStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordOpenWrapServerPanicStats), arg0, arg1) +} + +// RecordPBSAuctionRequestsStats mocks base method. +func (m *MockMetricsEngine) RecordPBSAuctionRequestsStats() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPBSAuctionRequestsStats") +} + +// RecordPBSAuctionRequestsStats indicates an expected call of RecordPBSAuctionRequestsStats. +func (mr *MockMetricsEngineMockRecorder) RecordPBSAuctionRequestsStats() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPBSAuctionRequestsStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPBSAuctionRequestsStats)) +} + +// RecordPartnerConfigErrors mocks base method. +func (m *MockMetricsEngine) RecordPartnerConfigErrors(arg0, arg1, arg2 string, arg3 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPartnerConfigErrors", arg0, arg1, arg2, arg3) +} + +// RecordPartnerConfigErrors indicates an expected call of RecordPartnerConfigErrors. +func (mr *MockMetricsEngineMockRecorder) RecordPartnerConfigErrors(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerConfigErrors", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerConfigErrors), arg0, arg1, arg2, arg3) +} + +// RecordPartnerResponseErrors mocks base method. +func (m *MockMetricsEngine) RecordPartnerResponseErrors(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPartnerResponseErrors", arg0, arg1, arg2) +} + +// RecordPartnerResponseErrors indicates an expected call of RecordPartnerResponseErrors. +func (mr *MockMetricsEngineMockRecorder) RecordPartnerResponseErrors(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerResponseErrors", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerResponseErrors), arg0, arg1, arg2) +} + +// RecordPartnerResponseTimeStats mocks base method. +func (m *MockMetricsEngine) RecordPartnerResponseTimeStats(arg0, arg1 string, arg2 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPartnerResponseTimeStats", arg0, arg1, arg2) +} + +// RecordPartnerResponseTimeStats indicates an expected call of RecordPartnerResponseTimeStats. +func (mr *MockMetricsEngineMockRecorder) RecordPartnerResponseTimeStats(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerResponseTimeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerResponseTimeStats), arg0, arg1, arg2) +} + +// RecordPartnerTimeoutInPBS mocks base method. +func (m *MockMetricsEngine) RecordPartnerTimeoutInPBS(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPartnerTimeoutInPBS", arg0, arg1, arg2) +} + +// RecordPartnerTimeoutInPBS indicates an expected call of RecordPartnerTimeoutInPBS. +func (mr *MockMetricsEngineMockRecorder) RecordPartnerTimeoutInPBS(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerTimeoutInPBS", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerTimeoutInPBS), arg0, arg1, arg2) +} + +// RecordPartnerTimeoutRequests mocks base method. +func (m *MockMetricsEngine) RecordPartnerTimeoutRequests(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPartnerTimeoutRequests", arg0, arg1, arg2) +} + +// RecordPartnerTimeoutRequests indicates an expected call of RecordPartnerTimeoutRequests. +func (mr *MockMetricsEngineMockRecorder) RecordPartnerTimeoutRequests(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerTimeoutRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerTimeoutRequests), arg0, arg1, arg2) +} + +// RecordPlatformPublisherPartnerReqStats mocks base method. +func (m *MockMetricsEngine) RecordPlatformPublisherPartnerReqStats(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPlatformPublisherPartnerReqStats", arg0, arg1, arg2) +} + +// RecordPlatformPublisherPartnerReqStats indicates an expected call of RecordPlatformPublisherPartnerReqStats. +func (mr *MockMetricsEngineMockRecorder) RecordPlatformPublisherPartnerReqStats(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPlatformPublisherPartnerReqStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPlatformPublisherPartnerReqStats), arg0, arg1, arg2) +} + +// RecordPlatformPublisherPartnerResponseStats mocks base method. +func (m *MockMetricsEngine) RecordPlatformPublisherPartnerResponseStats(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPlatformPublisherPartnerResponseStats", arg0, arg1, arg2) +} + +// RecordPlatformPublisherPartnerResponseStats indicates an expected call of RecordPlatformPublisherPartnerResponseStats. +func (mr *MockMetricsEngineMockRecorder) RecordPlatformPublisherPartnerResponseStats(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPlatformPublisherPartnerResponseStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPlatformPublisherPartnerResponseStats), arg0, arg1, arg2) +} + +// RecordPreProcessingTimeStats mocks base method. +func (m *MockMetricsEngine) RecordPreProcessingTimeStats(arg0 string, arg1 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPreProcessingTimeStats", arg0, arg1) +} + +// RecordPreProcessingTimeStats indicates an expected call of RecordPreProcessingTimeStats. +func (mr *MockMetricsEngineMockRecorder) RecordPreProcessingTimeStats(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPreProcessingTimeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPreProcessingTimeStats), arg0, arg1) +} + +// RecordPrebidTimeoutRequests mocks base method. +func (m *MockMetricsEngine) RecordPrebidTimeoutRequests(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPrebidTimeoutRequests", arg0, arg1) +} + +// RecordPrebidTimeoutRequests indicates an expected call of RecordPrebidTimeoutRequests. +func (mr *MockMetricsEngineMockRecorder) RecordPrebidTimeoutRequests(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPrebidTimeoutRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPrebidTimeoutRequests), arg0, arg1) +} + +// RecordPublisherInvalidProfileImpressions mocks base method. +func (m *MockMetricsEngine) RecordPublisherInvalidProfileImpressions(arg0, arg1 string, arg2 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPublisherInvalidProfileImpressions", arg0, arg1, arg2) +} + +// RecordPublisherInvalidProfileImpressions indicates an expected call of RecordPublisherInvalidProfileImpressions. +func (mr *MockMetricsEngineMockRecorder) RecordPublisherInvalidProfileImpressions(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherInvalidProfileImpressions", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherInvalidProfileImpressions), arg0, arg1, arg2) +} + +// RecordPublisherInvalidProfileRequests mocks base method. +func (m *MockMetricsEngine) RecordPublisherInvalidProfileRequests(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPublisherInvalidProfileRequests", arg0, arg1, arg2) +} + +// RecordPublisherInvalidProfileRequests indicates an expected call of RecordPublisherInvalidProfileRequests. +func (mr *MockMetricsEngineMockRecorder) RecordPublisherInvalidProfileRequests(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherInvalidProfileRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherInvalidProfileRequests), arg0, arg1, arg2) +} + +// RecordPublisherPartnerNoCookieStats mocks base method. +func (m *MockMetricsEngine) RecordPublisherPartnerNoCookieStats(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPublisherPartnerNoCookieStats", arg0, arg1) +} + +// RecordPublisherPartnerNoCookieStats indicates an expected call of RecordPublisherPartnerNoCookieStats. +func (mr *MockMetricsEngineMockRecorder) RecordPublisherPartnerNoCookieStats(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherPartnerNoCookieStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherPartnerNoCookieStats), arg0, arg1) +} + +// RecordPublisherProfileRequests mocks base method. +func (m *MockMetricsEngine) RecordPublisherProfileRequests(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPublisherProfileRequests", arg0, arg1) +} + +// RecordPublisherProfileRequests indicates an expected call of RecordPublisherProfileRequests. +func (mr *MockMetricsEngineMockRecorder) RecordPublisherProfileRequests(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherProfileRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherProfileRequests), arg0, arg1) +} + +// RecordPublisherRequests mocks base method. +func (m *MockMetricsEngine) RecordPublisherRequests(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPublisherRequests", arg0, arg1, arg2) +} + +// RecordPublisherRequests indicates an expected call of RecordPublisherRequests. +func (mr *MockMetricsEngineMockRecorder) RecordPublisherRequests(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherRequests), arg0, arg1, arg2) +} + +// RecordPublisherResponseEncodingErrorStats mocks base method. +func (m *MockMetricsEngine) RecordPublisherResponseEncodingErrorStats(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPublisherResponseEncodingErrorStats", arg0) +} + +// RecordPublisherResponseEncodingErrorStats indicates an expected call of RecordPublisherResponseEncodingErrorStats. +func (mr *MockMetricsEngineMockRecorder) RecordPublisherResponseEncodingErrorStats(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherResponseEncodingErrorStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherResponseEncodingErrorStats), arg0) +} + +// RecordPublisherResponseTimeStats mocks base method. +func (m *MockMetricsEngine) RecordPublisherResponseTimeStats(arg0 string, arg1 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPublisherResponseTimeStats", arg0, arg1) +} + +// RecordPublisherResponseTimeStats indicates an expected call of RecordPublisherResponseTimeStats. +func (mr *MockMetricsEngineMockRecorder) RecordPublisherResponseTimeStats(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherResponseTimeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherResponseTimeStats), arg0, arg1) +} + +// RecordPublisherWrapperLoggerFailure mocks base method. +func (m *MockMetricsEngine) RecordPublisherWrapperLoggerFailure(arg0, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordPublisherWrapperLoggerFailure", arg0, arg1, arg2) +} + +// RecordPublisherWrapperLoggerFailure indicates an expected call of RecordPublisherWrapperLoggerFailure. +func (mr *MockMetricsEngineMockRecorder) RecordPublisherWrapperLoggerFailure(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherWrapperLoggerFailure", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherWrapperLoggerFailure), arg0, arg1, arg2) +} + +// RecordReqImpsWithContentCount mocks base method. +func (m *MockMetricsEngine) RecordReqImpsWithContentCount(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordReqImpsWithContentCount", arg0, arg1) +} + +// RecordReqImpsWithContentCount indicates an expected call of RecordReqImpsWithContentCount. +func (mr *MockMetricsEngineMockRecorder) RecordReqImpsWithContentCount(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordReqImpsWithContentCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordReqImpsWithContentCount), arg0, arg1) +} + +// RecordRequest mocks base method. +func (m *MockMetricsEngine) RecordRequest(arg0 metrics.Labels) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordRequest", arg0) +} + +// RecordRequest indicates an expected call of RecordRequest. +func (mr *MockMetricsEngineMockRecorder) RecordRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordRequest", reflect.TypeOf((*MockMetricsEngine)(nil).RecordRequest), arg0) +} + +// RecordRequestAdPodGeneratedImpressionsCount mocks base method. +func (m *MockMetricsEngine) RecordRequestAdPodGeneratedImpressionsCount(arg0 int, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordRequestAdPodGeneratedImpressionsCount", arg0, arg1) +} + +// RecordRequestAdPodGeneratedImpressionsCount indicates an expected call of RecordRequestAdPodGeneratedImpressionsCount. +func (mr *MockMetricsEngineMockRecorder) RecordRequestAdPodGeneratedImpressionsCount(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordRequestAdPodGeneratedImpressionsCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordRequestAdPodGeneratedImpressionsCount), arg0, arg1) +} + +// RecordRequestTime mocks base method. +func (m *MockMetricsEngine) RecordRequestTime(arg0 string, arg1 time.Duration) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordRequestTime", arg0, arg1) +} + +// RecordRequestTime indicates an expected call of RecordRequestTime. +func (mr *MockMetricsEngineMockRecorder) RecordRequestTime(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordRequestTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordRequestTime), arg0, arg1) +} + +// RecordSSTimeoutRequests mocks base method. +func (m *MockMetricsEngine) RecordSSTimeoutRequests(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordSSTimeoutRequests", arg0, arg1) +} + +// RecordSSTimeoutRequests indicates an expected call of RecordSSTimeoutRequests. +func (mr *MockMetricsEngineMockRecorder) RecordSSTimeoutRequests(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordSSTimeoutRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordSSTimeoutRequests), arg0, arg1) +} + +// RecordSendLoggerDataTime mocks base method. +func (m *MockMetricsEngine) RecordSendLoggerDataTime(arg0, arg1 string, arg2 time.Duration) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordSendLoggerDataTime", arg0, arg1, arg2) +} + +// RecordSendLoggerDataTime indicates an expected call of RecordSendLoggerDataTime. +func (mr *MockMetricsEngineMockRecorder) RecordSendLoggerDataTime(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordSendLoggerDataTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordSendLoggerDataTime), arg0, arg1, arg2) +} + +// RecordStatsKeyCTVPrebidFailedImpression mocks base method. +func (m *MockMetricsEngine) RecordStatsKeyCTVPrebidFailedImpression(arg0 int, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordStatsKeyCTVPrebidFailedImpression", arg0, arg1, arg2) +} + +// RecordStatsKeyCTVPrebidFailedImpression indicates an expected call of RecordStatsKeyCTVPrebidFailedImpression. +func (mr *MockMetricsEngineMockRecorder) RecordStatsKeyCTVPrebidFailedImpression(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordStatsKeyCTVPrebidFailedImpression", reflect.TypeOf((*MockMetricsEngine)(nil).RecordStatsKeyCTVPrebidFailedImpression), arg0, arg1, arg2) +} + +// RecordUidsCookieNotPresentErrorStats mocks base method. +func (m *MockMetricsEngine) RecordUidsCookieNotPresentErrorStats(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordUidsCookieNotPresentErrorStats", arg0, arg1) +} + +// RecordUidsCookieNotPresentErrorStats indicates an expected call of RecordUidsCookieNotPresentErrorStats. +func (mr *MockMetricsEngineMockRecorder) RecordUidsCookieNotPresentErrorStats(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordUidsCookieNotPresentErrorStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordUidsCookieNotPresentErrorStats), arg0, arg1) +} + +// RecordVideoImpDisabledViaConnTypeStats mocks base method. +func (m *MockMetricsEngine) RecordVideoImpDisabledViaConnTypeStats(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordVideoImpDisabledViaConnTypeStats", arg0, arg1) +} + +// RecordVideoImpDisabledViaConnTypeStats indicates an expected call of RecordVideoImpDisabledViaConnTypeStats. +func (mr *MockMetricsEngineMockRecorder) RecordVideoImpDisabledViaConnTypeStats(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordVideoImpDisabledViaConnTypeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordVideoImpDisabledViaConnTypeStats), arg0, arg1) +} + +// RecordVideoInstlImpsStats mocks base method. +func (m *MockMetricsEngine) RecordVideoInstlImpsStats(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordVideoInstlImpsStats", arg0, arg1) +} + +// RecordVideoInstlImpsStats indicates an expected call of RecordVideoInstlImpsStats. +func (mr *MockMetricsEngineMockRecorder) RecordVideoInstlImpsStats(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordVideoInstlImpsStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordVideoInstlImpsStats), arg0, arg1) +} + +// Shutdown mocks base method. +func (m *MockMetricsEngine) Shutdown() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Shutdown") +} + +// Shutdown indicates an expected call of Shutdown. +func (mr *MockMetricsEngineMockRecorder) Shutdown() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockMetricsEngine)(nil).Shutdown)) +} diff --git a/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go b/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go new file mode 100644 index 00000000000..3a82bc6bd06 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go @@ -0,0 +1,487 @@ +package prometheus + +import ( + "strconv" + "sync" + "time" + + "github.com/prebid/prebid-server/v2/config" + "github.com/prometheus/client_golang/prometheus" +) + +// Metrics defines the Prometheus metrics backing the MetricsEngine implementation. +type Metrics struct { + + // general metrics + panics *prometheus.CounterVec + + // publisher-partner level metrics + pubPartnerNoCookie *prometheus.CounterVec + pubPartnerRespErrors *prometheus.CounterVec + pubPartnerConfigErrors *prometheus.CounterVec + pubPartnerInjectTrackerErrors *prometheus.CounterVec + pubPartnerResponseTimeSecs *prometheus.HistogramVec + + // publisher-profile level metrics + pubProfRequests *prometheus.CounterVec + pubProfInvalidImps *prometheus.CounterVec + pubProfUidsCookieAbsent *prometheus.CounterVec // TODO - really need this ? + pubProfVidInstlImps *prometheus.CounterVec // TODO - really need this ? + pubProfImpDisabledViaConfig *prometheus.CounterVec + + // publisher level metrics + pubRequestValidationErrors *prometheus.CounterVec // TODO : should we add profiles as label ? + pubNoBidResponseErrors *prometheus.CounterVec + pubResponseTime *prometheus.HistogramVec + pubImpsWithContent *prometheus.CounterVec + + // publisher-partner-platform level metrics + pubPartnerPlatformRequests *prometheus.CounterVec + pubPartnerPlatformResponses *prometheus.CounterVec + + // publisher-profile-endpoint level metrics + pubProfEndpointInvalidRequests *prometheus.CounterVec + + // endpoint level metrics + endpointBadRequest *prometheus.CounterVec //TODO: should we add pub+prof labels ; also NBR is INT should it be string + + // publisher-platform-endpoint level metrics + pubPlatformEndpointRequests *prometheus.CounterVec + + getProfileData *prometheus.HistogramVec + + dbQueryError *prometheus.CounterVec + + loggerFailure *prometheus.CounterVec + + //TODO -should we add "prefix" in metrics-name to differentiate it from prebid-core ? + + // sshb temporary + owRequests *prometheus.CounterVec + lurlSent *prometheus.CounterVec + lurlBatchSent *prometheus.CounterVec + ctvUaAccuracy *prometheus.CounterVec + bids *prometheus.CounterVec + prebidTimeoutRequests *prometheus.CounterVec + partnerTimeoutRequest *prometheus.CounterVec + panicCounts *prometheus.CounterVec + sendLoggerData *prometheus.HistogramVec + owRequestTime *prometheus.HistogramVec +} + +const ( + pubIDLabel = "pub_id" + profileIDLabel = "profile_id" + partnerLabel = "partner" + platformLabel = "platform" + endpointLabel = "endpoint" // TODO- apiTypeLabel ? + apiTypeLabel = "api_type" + impFormatLabel = "imp_format" //TODO -confirm ? + adFormatLabel = "ad_format" + sourceLabel = "source" //TODO -confirm ? + nbrLabel = "nbr" // TODO - errcode ? + errorLabel = "error" + hostLabel = "host" // combination of node:pod + methodLabel = "method" + queryTypeLabel = "query_type" +) + +var standardTimeBuckets = []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1} +var once sync.Once +var metric *Metrics + +// NewMetrics initializes a new Prometheus metrics instance. +func NewMetrics(cfg *config.PrometheusMetrics, promRegistry *prometheus.Registry) *Metrics { + once.Do(func() { + metric = newMetrics(cfg, promRegistry) + }) + return metric +} + +func newMetrics(cfg *config.PrometheusMetrics, promRegistry *prometheus.Registry) *Metrics { + metrics := Metrics{} + + // general metrics + metrics.panics = newCounter(cfg, promRegistry, + "panics", + "Count of prebid server panics in openwrap module.", + []string{hostLabel, methodLabel}, + ) + + // publisher-partner level metrics + // TODO : check description of this + metrics.pubPartnerNoCookie = newCounter(cfg, promRegistry, + "no_cookie", + "Count requests without cookie at publisher, partner level.", + []string{pubIDLabel, partnerLabel}, + ) + + metrics.pubPartnerRespErrors = newCounter(cfg, promRegistry, + "partner_response_error", + "Count publisher requests where partner responded with error.", + []string{pubIDLabel, partnerLabel, errorLabel}, + ) + + metrics.pubPartnerConfigErrors = newCounter(cfg, promRegistry, + "partner_config_errors", + "Count partner configuration errors at publisher, profile, partner level.", + []string{pubIDLabel, profileIDLabel, partnerLabel, errorLabel}, + ) + + metrics.pubPartnerInjectTrackerErrors = newCounter(cfg, promRegistry, + "inject_tracker_errors", + "Count of errors while injecting trackers at publisher, partner level.", + []string{pubIDLabel, partnerLabel, adFormatLabel}, + ) + + metrics.pubPartnerResponseTimeSecs = newHistogramVec(cfg, promRegistry, + "partner_response_time", + "Time taken by each partner to respond in seconds labeled by publisher.", + []string{pubIDLabel, partnerLabel}, + standardTimeBuckets, + ) + + // publisher-profile level metrics + metrics.pubProfRequests = newCounter(cfg, promRegistry, + "pub_profile_requests", + "Count total number of requests at publisher, profile level.", + []string{pubIDLabel, profileIDLabel}, + ) + + metrics.pubProfInvalidImps = newCounter(cfg, promRegistry, + "invalid_imps", + "Count impressions having invalid profile-id for respective publisher.", + []string{pubIDLabel, profileIDLabel}, + ) + + metrics.pubProfUidsCookieAbsent = newCounter(cfg, promRegistry, + "uids_cookie_absent", + "Count requests for which uids cookie is absent at publisher, profile level.", + []string{pubIDLabel, profileIDLabel}, + ) + + metrics.pubProfVidInstlImps = newCounter(cfg, promRegistry, + "vid_instl_imps", + "Count video interstitial impressions at publisher, profile level.", + []string{pubIDLabel, profileIDLabel}, + ) + + metrics.pubProfImpDisabledViaConfig = newCounter(cfg, promRegistry, + "imps_disabled_via_config", + "Count banner/video impressions disabled via config at publisher, profile level.", + []string{pubIDLabel, profileIDLabel, impFormatLabel}, + ) + + // publisher level metrics + metrics.pubRequestValidationErrors = newCounter(cfg, promRegistry, + "request_validation_errors", + "Count request validation failures along with NBR at publisher level.", + []string{pubIDLabel, nbrLabel}, + ) + + metrics.pubNoBidResponseErrors = newCounter(cfg, promRegistry, + "no_bid", + "Count of zero bid responses at publisher level.", + []string{pubIDLabel}, + ) + + metrics.pubResponseTime = newHistogramVec(cfg, promRegistry, + "pub_response_time", + "Total time taken by request in seconds at publisher level.", + []string{pubIDLabel}, + standardTimeBuckets, + ) + + metrics.pubImpsWithContent = newCounter(cfg, promRegistry, + "imps_with_content", + "Count impressions having app/site content at publisher level.", + []string{pubIDLabel, sourceLabel}, + //TODO - contentLabel ?? + ) + + // publisher-partner-platform metrics + metrics.pubPartnerPlatformRequests = newCounter(cfg, promRegistry, + "platform_requests", + "Count requests at publisher, partner, platform level.", + []string{pubIDLabel, partnerLabel, platformLabel}, + ) + metrics.pubPartnerPlatformResponses = newCounter(cfg, promRegistry, + "platform_responses", + "Count responses at publisher, partner, platform level.", + []string{pubIDLabel, partnerLabel, platformLabel}, + ) + + // publisher-profile-endpoint level metrics + metrics.pubProfEndpointInvalidRequests = newCounter(cfg, promRegistry, + "invalid_requests", + "Count invalid requests at publisher, profile, endpoint level.", + []string{pubIDLabel, profileIDLabel, endpointLabel}, + ) + + // endpoint level metrics + metrics.endpointBadRequest = newCounter(cfg, promRegistry, + "bad_requests", + "Count bad requests along with NBR code at endpoint level.", + []string{endpointLabel, nbrLabel}, + ) + + // publisher platform endpoint level metrics + metrics.pubPlatformEndpointRequests = newCounter(cfg, promRegistry, + "endpoint_requests", + "Count requests at publisher, platform, endpoint level.", + []string{pubIDLabel, platformLabel, endpointLabel}, + ) + + metrics.getProfileData = newHistogramVec(cfg, promRegistry, + "profile_data_get_time", + "Time taken to get the profile data in seconds", []string{endpointLabel, profileIDLabel}, + standardTimeBuckets) + + metrics.dbQueryError = newCounter(cfg, promRegistry, + "db_query_failed", + "Count failed db calls at profile, version level", + []string{queryTypeLabel, pubIDLabel, profileIDLabel}, + ) + + metrics.loggerFailure = newCounter(cfg, promRegistry, + "logger_send_failed", + "Count of failures to send the logger to analytics endpoint at publisher and profile level", + []string{pubIDLabel, profileIDLabel}, + ) + + newSSHBMetrics(&metrics, cfg, promRegistry) + + return &metrics +} + +func newCounter(cfg *config.PrometheusMetrics, registry *prometheus.Registry, name, help string, labels []string) *prometheus.CounterVec { + opts := prometheus.CounterOpts{ + Namespace: cfg.Namespace, + Subsystem: cfg.Subsystem, + Name: name, + Help: help, + } + counter := prometheus.NewCounterVec(opts, labels) + registry.MustRegister(counter) + return counter +} + +func newHistogramVec(cfg *config.PrometheusMetrics, registry *prometheus.Registry, name, help string, labels []string, buckets []float64) *prometheus.HistogramVec { + opts := prometheus.HistogramOpts{ + Namespace: cfg.Namespace, + Subsystem: cfg.Subsystem, + Name: name, + Help: help, + Buckets: buckets, + } + histogram := prometheus.NewHistogramVec(opts, labels) + registry.MustRegister(histogram) + return histogram +} + +func (m *Metrics) RecordOpenWrapServerPanicStats(hostName, method string) { + m.panics.With(prometheus.Labels{ + hostLabel: hostName, + methodLabel: method, + }).Inc() +} + +func (m *Metrics) RecordPublisherPartnerNoCookieStats(publisherID, partner string) { + m.pubPartnerNoCookie.With(prometheus.Labels{ + pubIDLabel: publisherID, + partnerLabel: partner, + }).Inc() +} + +func (m *Metrics) RecordPartnerResponseErrors(publisherID, partner, err string) { + m.pubPartnerRespErrors.With(prometheus.Labels{ + pubIDLabel: publisherID, + partnerLabel: partner, + errorLabel: err, + }).Inc() +} + +func (m *Metrics) RecordPartnerConfigErrors(publisherID, profileID, partner string, errcode int) { + m.pubPartnerConfigErrors.With(prometheus.Labels{ + pubIDLabel: publisherID, + profileIDLabel: profileID, + partnerLabel: partner, + errorLabel: strconv.Itoa(errcode), + }).Inc() +} + +func (m *Metrics) RecordPublisherProfileRequests(publisherID, profileID string) { + m.pubProfRequests.With(prometheus.Labels{ + pubIDLabel: publisherID, + profileIDLabel: profileID, + }).Inc() +} + +func (m *Metrics) RecordPublisherInvalidProfileImpressions(publisherID, profileID string, impCount int) { + m.pubProfInvalidImps.With(prometheus.Labels{ + pubIDLabel: publisherID, + profileIDLabel: profileID, + }).Add(float64(impCount)) +} + +func (m *Metrics) RecordNobidErrPrebidServerRequests(publisherID string, nbr int) { + m.pubRequestValidationErrors.With(prometheus.Labels{ + pubIDLabel: publisherID, + nbrLabel: strconv.Itoa(nbr), + }).Inc() +} + +func (m *Metrics) RecordNobidErrPrebidServerResponse(publisherID string) { + m.pubNoBidResponseErrors.With(prometheus.Labels{ + pubIDLabel: publisherID, + }).Inc() +} + +func (m *Metrics) RecordPlatformPublisherPartnerReqStats(platform, publisherID, partner string) { + m.pubPartnerPlatformRequests.With(prometheus.Labels{ + platformLabel: platform, + pubIDLabel: publisherID, + partnerLabel: partner, + }).Inc() +} + +func (m *Metrics) RecordPlatformPublisherPartnerResponseStats(platform, publisherID, partner string) { + m.pubPartnerPlatformResponses.With(prometheus.Labels{ + platformLabel: platform, + pubIDLabel: publisherID, + partnerLabel: partner, + }).Inc() +} + +func (m *Metrics) RecordPartnerResponseTimeStats(publisherID, partner string, responseTimeMs int) { + m.pubPartnerResponseTimeSecs.With(prometheus.Labels{ + pubIDLabel: publisherID, + partnerLabel: partner, + }).Observe(float64(responseTimeMs) / 1000) +} + +func (m *Metrics) RecordPublisherResponseTimeStats(publisherID string, responseTimeMs int) { + m.pubResponseTime.With(prometheus.Labels{ + pubIDLabel: publisherID, + }).Observe(float64(responseTimeMs) / 1000) +} + +func (m *Metrics) RecordPublisherInvalidProfileRequests(endpoint, publisherID, profileID string) { + m.pubProfEndpointInvalidRequests.With(prometheus.Labels{ + pubIDLabel: publisherID, + profileIDLabel: profileID, + endpointLabel: endpoint, + }).Inc() +} + +func (m *Metrics) RecordBadRequests(endpoint string, errorCode int) { + m.endpointBadRequest.With(prometheus.Labels{ + endpointLabel: endpoint, + nbrLabel: strconv.Itoa(errorCode), + }).Inc() +} + +func (m *Metrics) RecordUidsCookieNotPresentErrorStats(publisherID, profileID string) { + m.pubProfUidsCookieAbsent.With(prometheus.Labels{ + pubIDLabel: publisherID, + profileIDLabel: profileID, + }).Inc() +} + +func (m *Metrics) RecordVideoInstlImpsStats(publisherID, profileID string) { + m.pubProfVidInstlImps.With(prometheus.Labels{ + pubIDLabel: publisherID, + profileIDLabel: profileID, + }).Inc() +} + +func (m *Metrics) RecordImpDisabledViaConfigStats(impType, publisherID, profileID string) { + m.pubProfImpDisabledViaConfig.With(prometheus.Labels{ + pubIDLabel: publisherID, + profileIDLabel: profileID, + impFormatLabel: impType, + }).Inc() +} + +func (m *Metrics) RecordPublisherRequests(endpoint string, publisherID string, platform string) { + m.pubPlatformEndpointRequests.With(prometheus.Labels{ + pubIDLabel: publisherID, + platformLabel: platform, + endpointLabel: endpoint, + }).Inc() +} + +func (m *Metrics) RecordReqImpsWithContentCount(publisherID, content string) { + m.pubImpsWithContent.With(prometheus.Labels{ + pubIDLabel: publisherID, + sourceLabel: content, + }).Inc() +} + +func (m *Metrics) RecordInjectTrackerErrorCount(adformat, publisherID, partner string) { + m.pubPartnerInjectTrackerErrors.With(prometheus.Labels{ + adFormatLabel: adformat, + pubIDLabel: publisherID, + partnerLabel: partner, + }).Inc() +} + +// RecordGetProfileDataTime as a noop +func (m *Metrics) RecordGetProfileDataTime(endpoint, profileID string, getTime time.Duration) { + m.getProfileData.With(prometheus.Labels{ + endpointLabel: endpoint, + profileIDLabel: profileID, + }).Observe(float64(getTime.Seconds())) +} + +// RecordDBQueryFailure as a noop +func (m *Metrics) RecordDBQueryFailure(queryType, publisher, profile string) { + m.dbQueryError.With(prometheus.Labels{ + queryTypeLabel: queryType, + pubIDLabel: publisher, + profileIDLabel: profile, + }).Inc() +} + +// RecordPublisherWrapperLoggerFailure to record count of owlogger failures +func (m *Metrics) RecordPublisherWrapperLoggerFailure(publisher, profile, version string) { + m.loggerFailure.With(prometheus.Labels{ + pubIDLabel: publisher, + profileIDLabel: profile, + }).Inc() +} + +// TODO - really need ? +func (m *Metrics) RecordPBSAuctionRequestsStats() {} + +// TODO - empty because only stats are used currently +func (m *Metrics) RecordBidResponseByDealCountInPBS(publisherID, profile, aliasBidder, dealId string) { +} +func (m *Metrics) RecordBidResponseByDealCountInHB(publisherID, profile, aliasBidder, dealId string) { +} + +// TODO - remove this functions once we are completely migrated from Header-bidding to module +func (m *Metrics) RecordSSTimeoutRequests(publisherID, profileID string) {} +func (m *Metrics) RecordPartnerTimeoutInPBS(publisherID, profile, aliasBidder string) {} +func (m *Metrics) RecordPreProcessingTimeStats(publisherID string, processingTime int) {} +func (m *Metrics) RecordInvalidCreativeStats(publisherID, partner string) {} + +// Code is not migrated yet +func (m *Metrics) RecordVideoImpDisabledViaConnTypeStats(publisherID, profileID string) {} +func (m *Metrics) RecordCacheErrorRequests(endpoint string, publisherID string, profileID string) {} +func (m *Metrics) RecordPublisherResponseEncodingErrorStats(publisherID string) {} + +// CTV_specific metrics +func (m *Metrics) RecordCTVRequests(endpoint string, platform string) {} +func (m *Metrics) RecordCTVHTTPMethodRequests(endpoint string, publisherID string, method string) {} +func (m *Metrics) RecordCTVInvalidReasonCount(errorCode int, publisherID string) {} +func (m *Metrics) RecordCTVReqImpsWithDbConfigCount(publisherID string) {} +func (m *Metrics) RecordCTVReqImpsWithReqConfigCount(publisherID string) {} +func (m *Metrics) RecordAdPodGeneratedImpressionsCount(impCount int, publisherID string) {} +func (m *Metrics) RecordRequestAdPodGeneratedImpressionsCount(impCount int, publisherID string) {} +func (m *Metrics) RecordAdPodImpressionYield(maxDuration int, minDuration int, publisherID string) {} +func (m *Metrics) RecordCTVReqCountWithAdPod(publisherID, profileID string) {} +func (m *Metrics) RecordStatsKeyCTVPrebidFailedImpression(errorcode int, publisherID string, profile string) { +} + +func (m *Metrics) Shutdown() {} diff --git a/modules/pubmatic/openwrap/metrics/prometheus/prometheus_sshb.go b/modules/pubmatic/openwrap/metrics/prometheus/prometheus_sshb.go new file mode 100644 index 00000000000..8f29693475a --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/prometheus/prometheus_sshb.go @@ -0,0 +1,272 @@ +package prometheus + +import ( + "time" + + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + + "github.com/prometheus/client_golang/prometheus" +) + +const ( + bidderLabel = "bidder" + profileLabel = "profileid" + dealLabel = "deal" + nodeal = "nodeal" +) + +const ( + requestStatusLabel = "request_status" + requestTypeLabel = "request_type" + pubIdLabel = "pub_id" + partnerLable = "partner" + statusLabel = "status" + nodeNameLabel = "node_name" + podNameLabel = "pod_name" + methodNameLabel = "method_name" +) + +// The request types (endpoints) +const ( + ReqTypeORTB25Web metrics.RequestType = "openrtb25-web" + ReqTypeORTB25App metrics.RequestType = "openrtb25-app" + ReqTypeAMP metrics.RequestType = "amp" + ReqTypeVideo metrics.RequestType = "video" +) + +// Request/return status +const ( + RequestStatusOK metrics.RequestStatus = "ok" + RequestStatusBadInput metrics.RequestStatus = "badinput" + RequestStatusErr metrics.RequestStatus = "err" +) + +// RequestTypes returns all possible values for metrics.RequestType +func RequestTypes() []metrics.RequestType { + return []metrics.RequestType{ + ReqTypeORTB25Web, + ReqTypeORTB25App, + ReqTypeAMP, + ReqTypeVideo, + } +} + +// RequestStatuses return all possible values for metrics.RequestStatus +func RequestStatuses() []metrics.RequestStatus { + return []metrics.RequestStatus{ + RequestStatusOK, + RequestStatusBadInput, + RequestStatusErr, + } +} + +// newSSHBMetrics initializes a new Prometheus metrics instance with preloaded label values for SSHB service +func newSSHBMetrics(metrics *Metrics, cfg *config.PrometheusMetrics, promRegistry *prometheus.Registry) { + metrics.owRequests = newCounter(cfg, promRegistry, + "sshb_requests", + "Count of total requests to header-bidding server labeled by type and status.", + []string{requestTypeLabel, requestStatusLabel}) + + metrics.sendLoggerData = newHistogramVec(cfg, promRegistry, + "sshb_logger_data_send_time", + "Time taken to send the wrapper logger body in seconds", []string{endpointLabel, profileIDLabel}, + standardTimeBuckets) + + metrics.owRequestTime = newHistogramVec(cfg, promRegistry, + "sshb_request_time", + "Time taken to serve the request in seconds", []string{apiTypeLabel}, + []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1}) + + metrics.lurlSent = newCounter(cfg, promRegistry, "sshb_lurl_sent", "Count of lurl success, fail, drop and channel_full request sent labeled by publisherID, partner", []string{pubIdLabel, partnerLable, statusLabel}) + + metrics.lurlBatchSent = newCounter(cfg, promRegistry, "sshb_lurl_batch_sent", "Count of lurl Batch success, fail and drop request sent to wtracker ", []string{statusLabel}) + + metrics.bids = newCounter(cfg, promRegistry, + "sshb_bids", + "Count of bids by publisher id, profile, bidder and deal", + []string{pubIDLabel, profileLabel, bidderLabel, dealLabel}) + + metrics.prebidTimeoutRequests = newCounter(cfg, promRegistry, + "sshb_request_prebid_timeout", + "count no of requests in which prebid timeouts", + []string{pubIDLabel, profileLabel}) + + metrics.partnerTimeoutRequest = newCounter(cfg, promRegistry, + "sshb_request_partner_timeout", + "count no of requests in which partner timeouts", + []string{pubIDLabel, profileLabel, bidderLabel}) + + metrics.ctvUaAccuracy = newCounter(cfg, promRegistry, + "sshb_ctv_user_agent_accuracy", + "Count of requests detected by Ctv user agent regex labeled by pub id and status.", + []string{pubIdLabel, statusLabel}) + + metrics.panicCounts = newCounter(cfg, promRegistry, + "sshb_panic", + "Counts the header-bidding server panic.", + []string{nodeNameLabel, podNameLabel, methodNameLabel, endpointLabel}) + + preloadLabelValues(metrics) +} + +// RecordRequest across all engines +func (m *Metrics) RecordRequest(labels metrics.Labels) { + m.owRequests.With(prometheus.Labels{ + requestTypeLabel: string(labels.RType), + requestStatusLabel: string(labels.RequestStatus), + }).Inc() +} + +// RecordLurlSent records lurl status success, fail, drop and channel_fool +func (m *Metrics) RecordLurlSent(labels metrics.LurlStatusLabels) { + m.lurlSent.With(prometheus.Labels{ + pubIdLabel: labels.PublisherID, + partnerLable: labels.Partner, + statusLabel: labels.Status, + }).Inc() +} + +// RecordLurlBatchSent records lurl batchs sent to wtracker +func (m *Metrics) RecordLurlBatchSent(labels metrics.LurlBatchStatusLabels) { + m.lurlBatchSent.With(prometheus.Labels{ + statusLabel: labels.Status, + }).Inc() +} + +// RecordBids records count of bids labeled by pubid, profileid, bidder and deal +func (m *Metrics) RecordBids(pubid, profileid, bidder, deal string) { + m.bids.With(prometheus.Labels{ + pubIDLabel: pubid, + profileLabel: profileid, + bidderLabel: bidder, + dealLabel: deal, + }).Inc() +} + +// RecordPrebidTimeoutRequests records count of request in which prebid timedout based on pubid and profileid +func (m *Metrics) RecordPrebidTimeoutRequests(pubid, profileid string) { + m.prebidTimeoutRequests.With(prometheus.Labels{ + pubIDLabel: pubid, + profileLabel: profileid, + }).Inc() +} + +// RecordPartnerTimeoutRequests records count of Parnter timeout based on pubid, profileid and bidder +func (m *Metrics) RecordPartnerTimeoutRequests(pubid, profileid, bidder string) { + m.partnerTimeoutRequest.With(prometheus.Labels{ + pubIDLabel: pubid, + profileLabel: profileid, + bidderLabel: bidder, + }).Inc() +} + +// RecordCtvUaAccuracy records accuracy of the ctv user agents +func (m *Metrics) RecordCtvUaAccuracy(pubId, status string) { + m.ctvUaAccuracy.With(prometheus.Labels{ + pubIdLabel: pubId, + statusLabel: status, + }).Inc() +} + +// RecordSendLoggerDataTime as a noop +func (m *Metrics) RecordSendLoggerDataTime(endpoint, profileID string, sendTime time.Duration) { + m.sendLoggerData.With(prometheus.Labels{ + endpointLabel: endpoint, + profileIDLabel: profileID, + }).Observe(float64(sendTime.Seconds())) +} + +// RecordSendLoggerDataTime as a noop +func (m *Metrics) RecordRequestTime(requestType string, requestTime time.Duration) { + m.owRequestTime.With(prometheus.Labels{ + apiTypeLabel: requestType, + }).Observe(float64(requestTime.Seconds())) +} + +// RecordOWServerPanic counts the hb server panic +func (m *Metrics) RecordOWServerPanic(endpoint, methodName, nodeName, podName string) { + m.panicCounts.With(prometheus.Labels{ + endpointLabel: endpoint, + methodNameLabel: methodName, + nodeNameLabel: nodeName, + podNameLabel: podName, + }).Inc() +} + +func preloadLabelValues(m *Metrics) { + var ( + requestStatusValues = requestStatusesAsString() + requestTypeValues = requestTypesAsString() + ) + + preloadLabelValuesForCounter(m.owRequests, map[string][]string{ + requestTypeLabel: requestTypeValues, + requestStatusLabel: requestStatusValues, + }) +} + +func preloadLabelValuesForCounter(counter *prometheus.CounterVec, labelsWithValues map[string][]string) { + registerLabelPermutations(labelsWithValues, func(labels prometheus.Labels) { + counter.With(labels) + }) +} + +func registerLabelPermutations(labelsWithValues map[string][]string, register func(prometheus.Labels)) { + if len(labelsWithValues) == 0 { + return + } + + keys := make([]string, 0, len(labelsWithValues)) + values := make([][]string, 0, len(labelsWithValues)) + for k, v := range labelsWithValues { + keys = append(keys, k) + values = append(values, v) + } + + labels := prometheus.Labels{} + registerLabelPermutationsRecursive(0, keys, values, labels, register) +} + +func registerLabelPermutationsRecursive(depth int, keys []string, values [][]string, labels prometheus.Labels, register func(prometheus.Labels)) { + label := keys[depth] + isLeaf := depth == len(keys)-1 + + if isLeaf { + for _, v := range values[depth] { + labels[label] = v + register(cloneLabels(labels)) + } + } else { + for _, v := range values[depth] { + labels[label] = v + registerLabelPermutationsRecursive(depth+1, keys, values, labels, register) + } + } +} + +func cloneLabels(labels prometheus.Labels) prometheus.Labels { + clone := prometheus.Labels{} + for k, v := range labels { + clone[k] = v + } + return clone +} + +func requestStatusesAsString() []string { + values := RequestStatuses() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + +func requestTypesAsString() []string { + values := RequestTypes() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} diff --git a/modules/pubmatic/openwrap/metrics/prometheus/prometheus_sshb_test.go b/modules/pubmatic/openwrap/metrics/prometheus/prometheus_sshb_test.go new file mode 100644 index 00000000000..a5b2de76050 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/prometheus/prometheus_sshb_test.go @@ -0,0 +1,389 @@ +package prometheus + +import ( + "testing" + + "time" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" +) + +func TestRequestMetric(t *testing.T) { + m := createMetricsForTesting() + + requestType := ReqTypeORTB25Web + requestStatus := RequestStatusOK + + m.RecordRequest(metrics.Labels{ + RType: requestType, + RequestStatus: requestStatus, + }) + + expectedCount := float64(1) + assertCounterVecValue(t, "", "requests", m.owRequests, + expectedCount, + prometheus.Labels{ + requestTypeLabel: string(requestType), + requestStatusLabel: string(requestStatus), + }) +} + +func TestMetrics_RecordLurlSent(t *testing.T) { + m := createMetricsForTesting() + + type args struct { + labels metrics.LurlStatusLabels + } + tests := []struct { + name string + args args + }{ + { + name: "LurSent success", + args: args{ + labels: metrics.LurlStatusLabels{ + PublisherID: "123", + Partner: "pubmatic", + Status: "success", + }, + }, + }, + { + name: "LurSent fail", + args: args{ + labels: metrics.LurlStatusLabels{ + PublisherID: "123", + Partner: "pubmatic", + Status: "fail", + }, + }, + }, + { + name: "LurSent drop", + args: args{ + labels: metrics.LurlStatusLabels{ + PublisherID: "123", + Partner: "pubmatic", + Status: "drop", + }, + }, + }, + { + name: "LurSent channel_full", + args: args{ + labels: metrics.LurlStatusLabels{ + PublisherID: "123", + Partner: "pubmatic", + Status: "channel_full", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.RecordLurlSent(tt.args.labels) + assertCounterVecValue(t, "", "lurl_sent", m.lurlSent, float64(1), prometheus.Labels{ + pubIdLabel: tt.args.labels.PublisherID, + partnerLable: tt.args.labels.Partner, + statusLabel: tt.args.labels.Status, + }) + }) + } +} + +func TestMetrics_RecordLurlBatchSent(t *testing.T) { + m := createMetricsForTesting() + + type args struct { + labels metrics.LurlBatchStatusLabels + } + tests := []struct { + name string + args args + }{ + { + name: "LurBatchSent success", + args: args{ + labels: metrics.LurlBatchStatusLabels{ + Status: "success", + }, + }, + }, + { + name: "LurBatchSent fail", + args: args{ + labels: metrics.LurlBatchStatusLabels{ + Status: "fail", + }, + }, + }, + { + name: "LurBatchSent drop", + args: args{ + labels: metrics.LurlBatchStatusLabels{ + Status: "drop", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.RecordLurlBatchSent(tt.args.labels) + assertCounterVecValue(t, "", "lurl_batch_sent", m.lurlBatchSent, float64(1), prometheus.Labels{ + statusLabel: tt.args.labels.Status, + }) + }) + } +} + +func TestMetrics_RecordCtvUaAccuracy(t *testing.T) { + + m := createMetricsForTesting() + + type args struct { + pubId string + status string + } + tests := []struct { + name string + args args + }{ + { + name: "Regex detect ctv user agent correctly", + args: args{ + pubId: "1020", + status: "success", + }, + }, + { + name: "Regex detect ctv user agent incorrectly", + args: args{ + pubId: "1020", + status: "failure", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + m.RecordCtvUaAccuracy(tt.args.pubId, tt.args.status) + assertCounterVecValue(t, "", "ctv user agent accuracy", m.ctvUaAccuracy, float64(1), prometheus.Labels{ + pubIdLabel: tt.args.pubId, + statusLabel: tt.args.status, + }) + }) + } +} + +func TestRecordBids(t *testing.T) { + m := createMetricsForTesting() + + type args struct { + pubid, profid, bidder, deal string + } + tests := []struct { + name string + args args + want float64 + }{ + { + name: "call_record_bids", + args: args{ + pubid: "1010", + profid: "11", + bidder: "pubmatic", + deal: "pubdeal", + }, + want: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.RecordBids(tt.args.pubid, tt.args.profid, tt.args.bidder, tt.args.deal) + assertCounterVecValue(t, "", "bids", m.bids, tt.want, prometheus.Labels{ + pubIDLabel: tt.args.pubid, + profileLabel: tt.args.profid, + bidderLabel: tt.args.bidder, + dealLabel: tt.args.deal, + }) + }) + } +} + +func TestRecordPrebidTimeoutRequests(t *testing.T) { + m := createMetricsForTesting() + + type args struct { + pubid, profid string + } + tests := []struct { + name string + args args + want float64 + }{ + { + name: "record_request_prebid_timeout", + args: args{ + pubid: "1010", + profid: "11", + }, + want: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.RecordPrebidTimeoutRequests(tt.args.pubid, tt.args.profid) + assertCounterVecValue(t, "", "request_prebid_timeout", m.prebidTimeoutRequests, tt.want, prometheus.Labels{ + pubIDLabel: tt.args.pubid, + profileLabel: tt.args.profid, + }) + }) + } +} + +func TestRecordPartnerTimeoutRequests(t *testing.T) { + m := createMetricsForTesting() + + type args struct { + pubid, profid, bidder string + } + tests := []struct { + name string + args args + want float64 + }{ + { + name: "record_request_prebid_timeout", + args: args{ + pubid: "1010", + profid: "11", + bidder: "pubmatic", + }, + want: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.RecordPartnerTimeoutRequests(tt.args.pubid, tt.args.profid, tt.args.bidder) + assertCounterVecValue(t, "", "request_partner_timeout", m.partnerTimeoutRequest, tt.want, prometheus.Labels{ + pubIDLabel: tt.args.pubid, + profileLabel: tt.args.profid, + bidderLabel: tt.args.bidder, + }) + }) + } +} + +func TestRecordSendLoggerDataTime(t *testing.T) { + m := createMetricsForTesting() + + m.RecordSendLoggerDataTime("v25", "59201", 300*time.Millisecond) + resultingHistogram := getHistogramFromHistogramVecByTwoKeys(m.sendLoggerData, + endpointLabel, "v25", profileIDLabel, "59201") + + assertHistogram(t, "sshb_logger_data_send_time", resultingHistogram, 1, 0.3) +} + +func TestRecordRequestTime(t *testing.T) { + m := createMetricsForTesting() + + m.RecordRequestTime("v25", time.Millisecond*250) + + result := getHistogramFromHistogramVec(m.owRequestTime, apiTypeLabel, "v25") + assertHistogram(t, "TestRecordRequestTime", result, 1, 0.25) +} + +func TestRecordOWServerPanic(t *testing.T) { + + m := createMetricsForTesting() + + type args struct { + endpoint string + methodName string + nodeName string + podName string + } + tests := []struct { + name string + args args + }{ + { + name: "Record Panic counts", + args: args{ + endpoint: "/test/endpoint", + methodName: "TestMethodName", + nodeName: "sfo2hyp084.sfo2.pubmatic.com", + podName: "ssheaderbidding-0-0-38-pr-26-2-k8s-5679748b7b-tqh42", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + m.RecordOWServerPanic(tt.args.endpoint, tt.args.methodName, tt.args.nodeName, tt.args.podName) + assertCounterVecValue(t, "", "panic", m.panicCounts, float64(1), prometheus.Labels{ + endpointLabel: tt.args.endpoint, + methodNameLabel: tt.args.methodName, + nodeNameLabel: tt.args.nodeName, + podNameLabel: tt.args.podName, + }) + }) + } +} + +func TestRegisterLabelPermutations(t *testing.T) { + testCases := []struct { + description string + labelsWithValues map[string][]string + expectedLabels []prometheus.Labels + }{ + { + description: "Empty set.", + labelsWithValues: map[string][]string{}, + expectedLabels: []prometheus.Labels{}, + }, + { + description: "Set of 1 label and 1 value.", + labelsWithValues: map[string][]string{ + "1": {"A"}, + }, + expectedLabels: []prometheus.Labels{ + {"1": "A"}, + }, + }, + { + description: "Set of 1 label and 2 values.", + labelsWithValues: map[string][]string{ + "1": {"A", "B"}, + }, + expectedLabels: []prometheus.Labels{ + {"1": "A"}, + {"1": "B"}, + }, + }, + { + description: "Set of 2 labels and 2 values.", + labelsWithValues: map[string][]string{ + "1": {"A", "B"}, + "2": {"C", "D"}, + }, + expectedLabels: []prometheus.Labels{ + {"1": "A", "2": "C"}, + {"1": "A", "2": "D"}, + {"1": "B", "2": "C"}, + {"1": "B", "2": "D"}, + }, + }, + } + + for _, test := range testCases { + resultLabels := []prometheus.Labels{} + registerLabelPermutations(test.labelsWithValues, func(label prometheus.Labels) { + resultLabels = append(resultLabels, label) + }) + + assert.ElementsMatch(t, test.expectedLabels, resultLabels) + } +} diff --git a/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go b/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go new file mode 100644 index 00000000000..066b10fb3a7 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go @@ -0,0 +1,391 @@ +package prometheus + +import ( + "strconv" + "testing" + "time" + + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/assert" +) + +func createMetricsForTesting() *Metrics { + return NewMetrics(&config.PrometheusMetrics{ + Port: 8080, + Namespace: "prebid", + Subsystem: "server", + }, prometheus.NewRegistry()) +} + +func TestRecordOpenWrapServerPanicStats(t *testing.T) { + m := createMetricsForTesting() + + m.RecordOpenWrapServerPanicStats("node:pod", "process") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "panics", m.panics, + expectedCount, + prometheus.Labels{ + hostLabel: "node:pod", + methodLabel: "process", + }) +} + +func TestRecordPartnerResponseErrors(t *testing.T) { + m := createMetricsForTesting() + + m.RecordPartnerResponseErrors("5890", "pubmatic", "timeout") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "partner_response_error", m.pubPartnerRespErrors, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + partnerLabel: "pubmatic", + errorLabel: "timeout", + }) +} + +func TestRecordPublisherPartnerNoCookieStats(t *testing.T) { + m := createMetricsForTesting() + + m.RecordPublisherPartnerNoCookieStats("5890", "pubmatic") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "no_cookie", m.pubPartnerNoCookie, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + partnerLabel: "pubmatic", + }) +} + +func TestRecordPartnerConfigErrors(t *testing.T) { + m := createMetricsForTesting() + + m.RecordPartnerConfigErrors("5890", "1234", "pubmatic", models.PartnerErrSlotNotMapped) + + expectedCount := float64(1) + assertCounterVecValue(t, "", "partner_config_errors", m.pubPartnerConfigErrors, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + partnerLabel: "pubmatic", + profileIDLabel: "1234", + errorLabel: strconv.Itoa(models.PartnerErrSlotNotMapped), + }) +} + +func TestRecordPublisherProfileRequests(t *testing.T) { + m := createMetricsForTesting() + + m.RecordPublisherProfileRequests("5890", "1234") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "pub_profile_requests", m.pubProfRequests, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + profileIDLabel: "1234", + }) +} + +func TestRecordPublisherInvalidProfileImpressions(t *testing.T) { + m := createMetricsForTesting() + + m.RecordPublisherInvalidProfileImpressions("5890", "1234", 3) + + expectedCount := float64(3) + assertCounterVecValue(t, "", "invalid_imps", m.pubProfInvalidImps, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + profileIDLabel: "1234", + }) +} + +func TestRecordNobidErrPrebidServerRequests(t *testing.T) { + m := createMetricsForTesting() + + m.RecordNobidErrPrebidServerRequests("5890", nbr.AllPartnerThrottled) + + expectedCount := float64(1) + assertCounterVecValue(t, "", "request_validation_errors", m.pubRequestValidationErrors, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + nbrLabel: strconv.Itoa(nbr.AllPartnerThrottled), + }) +} + +func TestRecordNobidErrPrebidServerResponse(t *testing.T) { + m := createMetricsForTesting() + + m.RecordNobidErrPrebidServerResponse("5890") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "no_bid", m.pubNoBidResponseErrors, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + }) +} + +func TestRecordPlatformPublisherPartnerReqStats(t *testing.T) { + m := createMetricsForTesting() + + m.RecordPlatformPublisherPartnerReqStats(models.PLATFORM_APP, "5890", "pubmatic") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "platform_requests", m.pubPartnerPlatformRequests, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + platformLabel: models.PLATFORM_APP, + partnerLabel: "pubmatic", + }) +} + +func TestRecordPlatformPublisherPartnerResponseStats(t *testing.T) { + m := createMetricsForTesting() + + m.RecordPlatformPublisherPartnerResponseStats(models.PLATFORM_APP, "5890", "pubmatic") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "platform_responses", m.pubPartnerPlatformResponses, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + platformLabel: models.PLATFORM_APP, + partnerLabel: "pubmatic", + }) +} + +func TestRecordPublisherInvalidProfileRequests(t *testing.T) { + m := createMetricsForTesting() + + m.RecordPublisherInvalidProfileRequests(models.EndpointV25, "5890", "1234") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "invalid_requests", m.pubProfEndpointInvalidRequests, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + endpointLabel: models.EndpointV25, + profileIDLabel: "1234", + }) +} + +func TestRecordBadRequests(t *testing.T) { + m := createMetricsForTesting() + + m.RecordBadRequests(models.EndpointV25, nbr.AllPartnerThrottled) + + expectedCount := float64(1) + assertCounterVecValue(t, "", "bad_requests", m.endpointBadRequest, + expectedCount, + prometheus.Labels{ + endpointLabel: models.EndpointV25, + nbrLabel: strconv.Itoa(nbr.AllPartnerThrottled), + }) +} + +func TestRecordUidsCookieNotPresentErrorStats(t *testing.T) { + m := createMetricsForTesting() + + m.RecordUidsCookieNotPresentErrorStats("5890", "1234") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "uids_cookie_absent", m.pubProfUidsCookieAbsent, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + profileIDLabel: "1234", + }) +} + +func TestRecordVideoInstlImpsStats(t *testing.T) { + m := createMetricsForTesting() + + m.RecordVideoInstlImpsStats("5890", "1234") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "vid_instl_imps", m.pubProfVidInstlImps, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + profileIDLabel: "1234", + }) +} + +func TestRecordImpDisabledViaConfigStats(t *testing.T) { + m := createMetricsForTesting() + + m.RecordImpDisabledViaConfigStats(models.ImpTypeBanner, "5890", "1234") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "imps_disabled_via_config", m.pubProfImpDisabledViaConfig, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + profileIDLabel: "1234", + impFormatLabel: models.ImpTypeBanner, + }) +} + +func TestRecordPublisherRequests(t *testing.T) { + m := createMetricsForTesting() + + m.RecordPublisherRequests(models.EndpointV25, "5890", models.PLATFORM_AMP) + + expectedCount := float64(1) + assertCounterVecValue(t, "", "endpoint_requests", m.pubPlatformEndpointRequests, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + platformLabel: models.PLATFORM_AMP, + endpointLabel: models.EndpointV25, + }) +} + +func TestRecordReqImpsWithContentCount(t *testing.T) { + m := createMetricsForTesting() + + m.RecordReqImpsWithContentCount("5890", models.ContentTypeSite) + + expectedCount := float64(1) + assertCounterVecValue(t, "", "imps_with_content", m.pubImpsWithContent, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + sourceLabel: models.ContentTypeSite, + }) +} + +func TestRecordInjectTrackerErrorCount(t *testing.T) { + m := createMetricsForTesting() + + m.RecordInjectTrackerErrorCount(models.Banner, "5890", "pubmatic") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "inject_tracker_errors", m.pubPartnerInjectTrackerErrors, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + adFormatLabel: models.Banner, + partnerLabel: "pubmatic", + }) +} + +func TestRecordPartnerResponseTimeStats(t *testing.T) { + m := createMetricsForTesting() + + m.RecordPartnerResponseTimeStats("5890", "pubmatic", 3000) + resultingHistogram := getHistogramFromHistogramVecByTwoKeys(m.pubPartnerResponseTimeSecs, + pubIDLabel, "5890", partnerLabel, "pubmatic") + + assertHistogram(t, "partner_response_time", resultingHistogram, 1, 3) +} + +func TestRecordPublisherResponseTimeStats(t *testing.T) { + m := createMetricsForTesting() + + m.RecordPublisherResponseTimeStats("5890", 3000) + resultingHistogram := getHistogramFromHistogramVec(m.pubResponseTime, + pubIDLabel, "5890") + + assertHistogram(t, "pub_response_time", resultingHistogram, 1, 3) +} + +func TestRecordGetProfileDataTime(t *testing.T) { + m := createMetricsForTesting() + + m.RecordGetProfileDataTime("v25", "59201", 300*time.Millisecond) + resultingHistogram := getHistogramFromHistogramVecByTwoKeys(m.getProfileData, + endpointLabel, "v25", profileIDLabel, "59201") + + assertHistogram(t, "sshb_profile_data_get_time", resultingHistogram, 1, 0.3) +} + +func TestRecordDBQueryFailure(t *testing.T) { + m := createMetricsForTesting() + + m.RecordDBQueryFailure(models.AdunitConfigForLiveVersion, "5890", "59201") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "sshb_db_query_failed", m.dbQueryError, + expectedCount, + prometheus.Labels{ + queryTypeLabel: models.AdunitConfigForLiveVersion, + pubIDLabel: "5890", + profileIDLabel: "59201", + }) +} + +func getHistogramFromHistogramVec(histogram *prometheus.HistogramVec, labelKey, labelValue string) dto.Histogram { + var result dto.Histogram + processMetrics(histogram, func(m dto.Metric) { + for _, label := range m.GetLabel() { + if label.GetName() == labelKey && label.GetValue() == labelValue { + result = *m.GetHistogram() + } + } + }) + return result +} + +func getHistogramFromHistogramVecByTwoKeys(histogram *prometheus.HistogramVec, label1Key, label1Value, label2Key, label2Value string) dto.Histogram { + var result dto.Histogram + processMetrics(histogram, func(m dto.Metric) { + for ind, label := range m.GetLabel() { + if label.GetName() == label1Key && label.GetValue() == label1Value { + valInd := ind + if ind == 1 { + valInd = 0 + } else { + valInd = 1 + } + if m.Label[valInd].GetName() == label2Key && m.Label[valInd].GetValue() == label2Value { + result = *m.GetHistogram() + } + } + } + }) + return result +} + +func processMetrics(collector prometheus.Collector, handler func(m dto.Metric)) { + collectorChan := make(chan prometheus.Metric) + go func() { + collector.Collect(collectorChan) + close(collectorChan) + }() + + for metric := range collectorChan { + dtoMetric := dto.Metric{} + metric.Write(&dtoMetric) + handler(dtoMetric) + } +} + +func assertHistogram(t *testing.T, name string, histogram dto.Histogram, expectedCount uint64, expectedSum float64) { + assert.Equal(t, expectedCount, histogram.GetSampleCount(), name+":count") + assert.Equal(t, expectedSum, histogram.GetSampleSum(), name+":sum") +} + +func assertCounterValue(t *testing.T, description, name string, counter prometheus.Counter, expected float64) { + m := dto.Metric{} + counter.Write(&m) + actual := *m.GetCounter().Value + + assert.Equal(t, expected, actual, description) +} + +func assertCounterVecValue(t *testing.T, description, name string, counterVec *prometheus.CounterVec, expected float64, labels prometheus.Labels) { + counter := counterVec.With(labels) + assertCounterValue(t, description, name, counter, expected) +} diff --git a/modules/pubmatic/openwrap/metrics/stats/client.go b/modules/pubmatic/openwrap/metrics/stats/client.go new file mode 100644 index 00000000000..c87825f0f54 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/stats/client.go @@ -0,0 +1,168 @@ +package stats + +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "net/http" + "time" + + "github.com/alitto/pond" + "github.com/golang/glog" +) + +type HttpClient interface { + Do(req *http.Request) (*http.Response, error) +} + +// TrySubmit attempts to send a task to this worker pool for execution. If the queue is full, +// it will not wait for a worker to become idle. It returns true if it was able to dispatch +// the task and false otherwise. +type WorkerPool interface { + TrySubmit(task func()) bool +} + +// Client is a StatClient. All stats related operation will be done using this. +type Client struct { + config *config + httpClient HttpClient + endpoint string + pubChan chan stat + pubTicker *time.Ticker + statMap map[string]int + shutDownChan chan struct{} + pool WorkerPool +} + +// NewClient will validate the Config provided and return a new Client +func NewClient(cfg *config) (*Client, error) { + if err := cfg.validate(); err != nil { + return nil, fmt.Errorf("invalid stats client configurations:%s", err.Error()) + } + + client := &http.Client{ + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: time.Duration(cfg.DialTimeout) * time.Second, + KeepAlive: time.Duration(cfg.KeepAliveDuration) * time.Minute, + }).DialContext, + MaxIdleConns: cfg.MaxIdleConns, + MaxIdleConnsPerHost: cfg.MaxIdleConnsPerHost, + ResponseHeaderTimeout: time.Duration(cfg.ResponseHeaderTimeout) * time.Second, + }, + } + + c := &Client{ + config: cfg, + httpClient: client, + endpoint: cfg.Endpoint, + pubChan: make(chan stat, cfg.MaxChannelLength), + pubTicker: time.NewTicker(time.Duration(cfg.PublishingInterval) * time.Minute), + statMap: make(map[string]int), + shutDownChan: make(chan struct{}), + pool: pond.New(cfg.PoolMaxWorkers, cfg.PoolMaxCapacity), + } + + go c.process() + + return c, nil +} + +// ShutdownProcess will perform the graceful shutdown operation +func (sc *Client) ShutdownProcess() { + sc.shutDownChan <- struct{}{} +} + +// PublishStat will push a stat to pubChan channel. +func (sc *Client) PublishStat(key string, value int) { + sc.pubChan <- stat{Key: key, Value: value} +} + +// process function will keep listening on the pubChan +// It will publish the stats to server if +// (1) number of stats reaches the PublishingThreshold or, +// (2) PublishingInterval timeout occurs +func (sc *Client) process() { + + for { + select { + case stat := <-sc.pubChan: + sc.statMap[stat.Key] = sc.statMap[stat.Key] + stat.Value + if len(sc.statMap) >= sc.config.PublishingThreshold { + sc.prepareStatsForPublishing() + sc.pubTicker.Reset(time.Duration(sc.config.PublishingInterval) * time.Minute) + } + + case <-sc.pubTicker.C: + sc.prepareStatsForPublishing() + + case <-sc.shutDownChan: + sc.prepareStatsForPublishing() + return + } + } +} + +// prepareStatsForPublishing creates copy of map containing stat-key and value +// and calls publishStatsToServer to publishes it to the stat-server +func (sc *Client) prepareStatsForPublishing() { + if len(sc.statMap) != 0 { + collectedStats := sc.statMap + sc.statMap = map[string]int{} + status := sc.pool.TrySubmit(func() { + sc.publishStatsToServer(collectedStats) + }) + if !status { + glog.Errorf("[stats_fail] Failed to submit the publishStatsToServer task containing %d record to pool", len(collectedStats)) + } + } +} + +// publishStatsToServer sends the stats to the stat-server +// in case of failure, it retries to send for Client.config.Retries number of times. +func (sc *Client) publishStatsToServer(statMap map[string]int) int { + + statJson, err := json.Marshal(statMap) + if err != nil { + glog.Errorf("[stats_fail] Json unmarshal fail: %v", err) + return statusSetupFail + } + + glog.V(3).Infof("[stats] nstats:[%d] data:[%s]", len(statMap), statJson) + req, err := http.NewRequest(http.MethodPost, sc.endpoint, bytes.NewBuffer(statJson)) + if err != nil { + glog.Errorf("[stats_fail] Failed to form request to sent stats to server: %v", err) + return statusSetupFail + } + + req.Header.Add(contentType, applicationJSON) + for retry := 0; retry < sc.config.Retries; retry++ { + + startTime := time.Now() + resp, err := sc.httpClient.Do(req) + elapsedTime := time.Since(startTime) + + code := 0 + if resp != nil { + code = resp.StatusCode + defer resp.Body.Close() + } + + if err == nil && code == http.StatusOK { + glog.Infof("[stats_success] retry:[%d] nstats:[%d] time:[%v]", retry, len(statMap), elapsedTime) + return statusPublishSuccess + } + + if retry == (sc.config.Retries - 1) { + glog.Errorf("[stats_fail] retry:[%d] status:[%d] nstats:[%d] time:[%v] error:[%v]", retry, code, len(statMap), elapsedTime, err) + break + } + + if sc.config.retryInterval > 0 { + time.Sleep(time.Duration(sc.config.retryInterval) * time.Second) + } + } + + return statusPublishFail +} diff --git a/modules/pubmatic/openwrap/metrics/stats/client_config.go b/modules/pubmatic/openwrap/metrics/stats/client_config.go new file mode 100644 index 00000000000..8c8cf90f8b2 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/stats/client_config.go @@ -0,0 +1,81 @@ +package stats + +import ( + "errors" +) + +// config will have the information required to initialise a stats client +type config struct { + Endpoint string // stat-server's endpoint + PublishingInterval int // interval (in minutes) to publish stats to server + PublishingThreshold int // publish stats if number of stat-records present in map is higher than this threshold + Retries int // max retries to publish stats to server + DialTimeout int // http connection dial-timeout (in seconds) + KeepAliveDuration int // http connection keep-alive-duration (in minutes) + MaxIdleConns int // maximum idle connections across all hosts + MaxIdleConnsPerHost int // maximum idle connections per host + retryInterval int // if failed to publish stat then wait for retryInterval seconds for next attempt + ResponseHeaderTimeout int // amount of time (in seconds) to wait for server's response header + MaxChannelLength int // max number of stat keys + PoolMaxWorkers int // max number of workers that will actually send the data to stats-server + PoolMaxCapacity int // number of tasks that can be submitted to the pool without blocking +} + +func (c *config) validate() (err error) { + if c.Endpoint == "" { + return errors.New("stat server endpoint cannot be empty") + } + + if c.PublishingInterval < minPublishingInterval { + c.PublishingInterval = minPublishingInterval + } else if c.PublishingInterval > maxPublishingInterval { + c.PublishingInterval = maxPublishingInterval + } + + if c.Retries > 0 { + if c.Retries > (c.PublishingInterval*60)/minRetryDuration { + c.Retries = (c.PublishingInterval * 60) / minRetryDuration + c.retryInterval = minRetryDuration + } else { + c.retryInterval = (c.PublishingInterval * 60) / c.Retries + } + } + + if c.DialTimeout < minDialTimeout { + c.DialTimeout = minDialTimeout + } + + if c.KeepAliveDuration < minKeepAliveDuration { + c.KeepAliveDuration = minKeepAliveDuration + } + + if c.MaxIdleConns < 0 { + c.MaxIdleConns = 0 + } + + if c.MaxIdleConnsPerHost < 0 { + c.MaxIdleConnsPerHost = 0 + } + + if c.PublishingThreshold < minPublishingThreshold { + c.PublishingThreshold = minPublishingThreshold + } + + if c.ResponseHeaderTimeout < minResponseHeaderTimeout { + c.ResponseHeaderTimeout = minResponseHeaderTimeout + } + + if c.MaxChannelLength < minChannelLength { + c.MaxChannelLength = minChannelLength + } + + if c.PoolMaxWorkers < minPoolWorker { + c.PoolMaxWorkers = minPoolWorker + } + + if c.PoolMaxCapacity < minPoolCapacity { + c.PoolMaxCapacity = minPoolCapacity + } + + return nil +} diff --git a/modules/pubmatic/openwrap/metrics/stats/client_config_test.go b/modules/pubmatic/openwrap/metrics/stats/client_config_test.go new file mode 100644 index 00000000000..f3a6393d0b6 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/stats/client_config_test.go @@ -0,0 +1,159 @@ +package stats + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidate(t *testing.T) { + + type args struct { + cfg *config + } + + type want struct { + err error + cfg *config + } + + tests := []struct { + name string + args args + want want + }{ + { + name: "empty_endpoint", + args: args{ + cfg: &config{ + Endpoint: "", + }, + }, + want: want{ + err: fmt.Errorf("stat server endpoint cannot be empty"), + cfg: &config{ + Endpoint: "", + }, + }, + }, + { + name: "lower_values_than_min_limit", + args: args{ + cfg: &config{ + Endpoint: "10.10.10.10/stat", + PublishingInterval: 0, + DialTimeout: 0, + KeepAliveDuration: 0, + MaxIdleConns: -1, + MaxIdleConnsPerHost: -1, + PublishingThreshold: 0, + }, + }, + want: want{ + err: nil, + cfg: &config{ + Endpoint: "10.10.10.10/stat", + PublishingInterval: minPublishingInterval, + DialTimeout: minDialTimeout, + KeepAliveDuration: minKeepAliveDuration, + MaxIdleConns: 0, + MaxIdleConnsPerHost: 0, + PublishingThreshold: minPublishingThreshold, + MaxChannelLength: minChannelLength, + ResponseHeaderTimeout: minResponseHeaderTimeout, + PoolMaxWorkers: minPoolWorker, + PoolMaxCapacity: minPoolCapacity, + }, + }, + }, + { + name: "high_PublishingInterval_than_max_limit", + args: args{ + cfg: &config{ + Endpoint: "10.10.10.10/stat", + PublishingInterval: 10, + }, + }, + want: want{ + err: nil, + cfg: &config{ + Endpoint: "10.10.10.10/stat", + PublishingInterval: maxPublishingInterval, + DialTimeout: minDialTimeout, + KeepAliveDuration: minKeepAliveDuration, + MaxIdleConns: 0, + MaxIdleConnsPerHost: 0, + PublishingThreshold: minPublishingThreshold, + MaxChannelLength: minChannelLength, + ResponseHeaderTimeout: minResponseHeaderTimeout, + PoolMaxWorkers: minPoolWorker, + PoolMaxCapacity: minPoolCapacity, + }, + }, + }, + { + name: "high_Retries_than_maxRetriesAllowed", + args: args{ + cfg: &config{ + Endpoint: "10.10.10.10/stat", + PublishingInterval: 3, + Retries: 100, + }, + }, + want: want{ + err: nil, + cfg: &config{ + Endpoint: "10.10.10.10/stat", + PublishingInterval: 3, + DialTimeout: minDialTimeout, + KeepAliveDuration: minKeepAliveDuration, + MaxIdleConns: 0, + MaxIdleConnsPerHost: 0, + PublishingThreshold: minPublishingThreshold, + Retries: 5, + retryInterval: minRetryDuration, + MaxChannelLength: minChannelLength, + ResponseHeaderTimeout: minResponseHeaderTimeout, + PoolMaxWorkers: minPoolWorker, + PoolMaxCapacity: minPoolCapacity, + }, + }, + }, + { + name: "valid_Retries_value", + args: args{ + cfg: &config{ + Endpoint: "10.10.10.10/stat", + PublishingInterval: 3, + Retries: 5, + }, + }, + want: want{ + err: nil, + cfg: &config{ + Endpoint: "10.10.10.10/stat", + PublishingInterval: 3, + DialTimeout: minDialTimeout, + KeepAliveDuration: minKeepAliveDuration, + MaxIdleConns: 0, + MaxIdleConnsPerHost: 0, + PublishingThreshold: minPublishingThreshold, + Retries: 5, + retryInterval: 36, + MaxChannelLength: minChannelLength, + ResponseHeaderTimeout: minResponseHeaderTimeout, + PoolMaxWorkers: minPoolWorker, + PoolMaxCapacity: minPoolCapacity, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.args.cfg.validate() + assert.Equal(t, err, tt.want.err, "Mismatched error") + assert.Equal(t, tt.args.cfg, tt.want.cfg, "Mismatched config") + }) + } +} diff --git a/modules/pubmatic/openwrap/metrics/stats/client_test.go b/modules/pubmatic/openwrap/metrics/stats/client_test.go new file mode 100644 index 00000000000..fa5c89168a8 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/stats/client_test.go @@ -0,0 +1,439 @@ +package stats + +import ( + "bytes" + "fmt" + "io" + "net" + "net/http" + "sync" + "testing" + "time" + + "github.com/alitto/pond" + "github.com/golang/mock/gomock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/stats/mock" + "github.com/stretchr/testify/assert" +) + +func TestNewClient(t *testing.T) { + + type args struct { + cfg *config + } + + type want struct { + err error + statClient *Client + } + + tests := []struct { + name string + args args + want want + }{ + { + name: "invalid_config", + args: args{ + cfg: &config{ + Endpoint: "", + }, + }, + want: want{ + err: fmt.Errorf("invalid stats client configurations:stat server endpoint cannot be empty"), + statClient: nil, + }, + }, + { + name: "valid_config", + args: args{ + cfg: &config{ + Endpoint: "10.10.10.10:8080/stat", + PublishingInterval: 3, + DialTimeout: minDialTimeout, + KeepAliveDuration: minKeepAliveDuration, + MaxIdleConns: 0, + MaxIdleConnsPerHost: 0, + PublishingThreshold: minPublishingThreshold, + Retries: 5, + retryInterval: 36, + }, + }, + want: want{ + err: nil, + statClient: &Client{ + config: &config{ + Endpoint: "10.10.10.10:8080/stat", + PublishingInterval: 3, + DialTimeout: minDialTimeout, + KeepAliveDuration: minKeepAliveDuration, + MaxIdleConns: 0, + MaxIdleConnsPerHost: 0, + PublishingThreshold: minPublishingThreshold, + Retries: 5, + retryInterval: 36, + MaxChannelLength: minChannelLength, + ResponseHeaderTimeout: minResponseHeaderTimeout, + PoolMaxWorkers: minPoolWorker, + PoolMaxCapacity: minPoolCapacity, + }, + httpClient: &http.Client{ + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: time.Duration(minDialTimeout) * time.Second, + KeepAlive: time.Duration(minKeepAliveDuration) * time.Minute, + }).DialContext, + MaxIdleConns: 0, + MaxIdleConnsPerHost: 0, + ResponseHeaderTimeout: 30 * time.Second, + }, + }, + endpoint: "10.10.10.10:8080/stat", + pubChan: make(chan stat, minChannelLength), + pubTicker: time.NewTicker(time.Duration(3) * time.Minute), + statMap: map[string]int{}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, err := NewClient(tt.args.cfg) + assert.Equal(t, tt.want.err, err, "Mismatched error") + compareClient(tt.want.statClient, client, t) + }) + } +} + +func compareClient(expectedClient, actualClient *Client, t *testing.T) { + + if expectedClient != nil && actualClient != nil { + assert.Equal(t, expectedClient.endpoint, actualClient.endpoint, "Mismatched endpoint") + assert.Equal(t, expectedClient.config, actualClient.config, "Mismatched config") + assert.Equal(t, cap(expectedClient.pubChan), cap(actualClient.pubChan), "Mismatched pubChan capacity") + assert.Equal(t, expectedClient.statMap, actualClient.statMap, "Mismatched statMap") + } + + if expectedClient != nil && actualClient == nil { + t.Errorf("actualClient is expected to be non-nil") + } + + if actualClient != nil && expectedClient == nil { + t.Errorf("actualClient is expected to be nil") + } +} + +func TestPublishStat(t *testing.T) { + + type args struct { + keyVal map[string]int + maxChanSize int + } + + type want struct { + keyVal map[string]int + channelSize int + } + + tests := []struct { + name string + args args + want want + }{ + { + name: "push_multiple_stat", + args: args{ + keyVal: map[string]int{ + "key1": 10, + "key2": 20, + }, + maxChanSize: 2, + }, + want: want{ + keyVal: map[string]int{ + "key1": 10, + "key2": 20, + }, + channelSize: 2, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := Client{ + pubChan: make(chan stat, tt.args.maxChanSize), + } + for k, v := range tt.args.keyVal { + client.PublishStat(k, v) + } + + close(client.pubChan) + assert.Equal(t, tt.want.channelSize, len(client.pubChan)) + for stat := range client.pubChan { + assert.Equal(t, stat.Value, tt.want.keyVal[stat.Key]) + } + }) + } +} + +func TestPrepareStatsForPublishing(t *testing.T) { + + type args struct { + client *Client + } + + tests := []struct { + name string + args args + expectedLength int + }{ + { + name: "statMap_should_be_empty", + args: args{ + client: &Client{ + statMap: map[string]int{ + "key1": 10, + "key2": 20, + }, + config: &config{ + Retries: 1, + }, + httpClient: http.DefaultClient, + pool: pond.New(2, 2), + }, + }, + expectedLength: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.args.client.prepareStatsForPublishing() + assert.Equal(t, len(tt.args.client.statMap), tt.expectedLength) + }) + } +} + +func TestPublishStatsToServer(t *testing.T) { + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockClient := mock.NewMockHttpClient(ctrl) + + type args struct { + statClient *Client + statsMap map[string]int + } + + tests := []struct { + name string + args args + expStatusCode int + setup func() + }{ + { + name: "invalid_url", + args: args{ + statClient: &Client{ + endpoint: "%%invalid%%url", + }, + statsMap: map[string]int{ + "key": 10, + }, + }, + setup: func() {}, + expStatusCode: statusSetupFail, + }, + { + name: "server_responds_with_error", + args: args{ + statClient: &Client{ + endpoint: "http://any-random-server.com", + config: &config{ + Retries: 1, + }, + httpClient: mockClient, + }, + statsMap: map[string]int{ + "key": 10, + }, + }, + setup: func() { + mockClient.EXPECT().Do(gomock.Any()).Return(&http.Response{StatusCode: 500, Body: io.NopCloser(bytes.NewReader(nil))}, nil) + }, + expStatusCode: statusPublishFail, + }, + { + name: "server_responds_with_error_multi_retries", + args: args{ + statClient: &Client{ + endpoint: "http://any-random-server.com", + config: &config{ + Retries: 3, + retryInterval: 1, + }, + httpClient: mockClient, + }, + statsMap: map[string]int{ + "key": 10, + }, + }, + setup: func() { + mockClient.EXPECT().Do(gomock.Any()).Return(&http.Response{StatusCode: 500, Body: io.NopCloser(bytes.NewReader(nil))}, nil).Times(3) + }, + expStatusCode: statusPublishFail, + }, + { + name: "first_attempt_fail_second_attempt_success", + args: args{ + statClient: &Client{ + endpoint: "http://any-random-server.com", + config: &config{ + Retries: 3, + retryInterval: 1, + }, + httpClient: mockClient, + }, + statsMap: map[string]int{ + "key": 10, + }, + }, + setup: func() { + gomock.InOrder( + mockClient.EXPECT().Do(gomock.Any()).Return(&http.Response{StatusCode: 500, Body: io.NopCloser(bytes.NewReader(nil))}, nil), + mockClient.EXPECT().Do(gomock.Any()).Return(&http.Response{StatusCode: 200, Body: io.NopCloser(bytes.NewReader(nil))}, nil), + ) + }, + expStatusCode: statusPublishSuccess, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setup() + statusCode := tt.args.statClient.publishStatsToServer(tt.args.statsMap) + assert.Equal(t, tt.expStatusCode, statusCode) + }) + } +} + +func TestProcess(t *testing.T) { + type args struct { + client *Client + } + + tests := []struct { + name string + args args + setup func(*Client) + getMockPool func(wg *sync.WaitGroup) (*gomock.Controller, WorkerPool) + }{ + { + name: "PublishingThreshold_limit_reached", + args: args{ + client: &Client{ + statMap: map[string]int{}, + config: &config{ + Retries: 1, + PublishingInterval: 1, + PublishingThreshold: 2, + }, + pubChan: make(chan stat, 2), + pubTicker: time.NewTicker(1 * time.Minute), + shutDownChan: make(chan struct{}), + }, + }, + setup: func(client *Client) { + client.PublishStat("key1", 1) + client.PublishStat("key2", 2) + }, + getMockPool: func(wg *sync.WaitGroup) (*gomock.Controller, WorkerPool) { + ctrl := gomock.NewController(t) + mockWorkerPool := mock.NewMockWorkerPool(ctrl) + mockWorkerPool.EXPECT().TrySubmit(gomock.Any()).DoAndReturn(func(task func()) bool { + wg.Done() + return true + }) + return ctrl, mockWorkerPool + }, + }, + { + name: "PublishingInterval_timer_timeouts", + args: args{ + client: &Client{ + statMap: map[string]int{}, + config: &config{ + Retries: 1, + PublishingInterval: 1, + PublishingThreshold: 10, + }, + pubChan: make(chan stat, 10), + pubTicker: time.NewTicker(1 * time.Second), + shutDownChan: make(chan struct{}), + }, + }, + setup: func(client *Client) { + client.PublishStat("key1", 1) + client.PublishStat("key2", 2) + }, + getMockPool: func(wg *sync.WaitGroup) (*gomock.Controller, WorkerPool) { + ctrl := gomock.NewController(t) + mockWorkerPool := mock.NewMockWorkerPool(ctrl) + mockWorkerPool.EXPECT().TrySubmit(gomock.Any()).DoAndReturn(func(task func()) bool { + wg.Done() + return true + }) + return ctrl, mockWorkerPool + }, + }, + { + name: "graceful_shutdown_process", + args: args{ + client: &Client{ + statMap: map[string]int{}, + config: &config{ + Retries: 1, + PublishingThreshold: 5, + }, + pubChan: make(chan stat, 10), + pubTicker: time.NewTicker(1 * time.Minute), + shutDownChan: make(chan struct{}), + }, + }, + setup: func(client *Client) { + client.PublishStat("key1", 1) + time.Sleep(1 * time.Second) + client.ShutdownProcess() + }, + getMockPool: func(wg *sync.WaitGroup) (*gomock.Controller, WorkerPool) { + ctrl := gomock.NewController(t) + mockWorkerPool := mock.NewMockWorkerPool(ctrl) + mockWorkerPool.EXPECT().TrySubmit(gomock.Any()).DoAndReturn(func(task func()) bool { + wg.Done() + return true + }) + return ctrl, mockWorkerPool + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var wg sync.WaitGroup + wg.Add(1) + + client := tt.args.client + ctrl, mockPool := tt.getMockPool(&wg) + defer ctrl.Finish() + client.pool = mockPool + + go client.process() + + tt.setup(client) + + wg.Wait() + }) + } +} diff --git a/modules/pubmatic/openwrap/metrics/stats/config.go b/modules/pubmatic/openwrap/metrics/stats/config.go new file mode 100644 index 00000000000..1b60c388e12 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/stats/config.go @@ -0,0 +1,18 @@ +package stats + +type Stats struct { + UseHostName bool // if true use actual_node_name:actual_pod_name into stats key + DefaultHostName string // combination of node:pod, default value is N:P + Endpoint string // stat-server's endpoint + PublishInterval int // interval (in minutes) to publish stats to server + PublishThreshold int // publish stats if number of stat-records present in map is higher than this threshold + Retries int // max retries to publish stats to server + DialTimeout int // http connection dial-timeout (in seconds) + KeepAliveDuration int // http connection keep-alive-duration (in minutes) + MaxIdleConnections int // maximum idle connections across all hosts + MaxIdleConnectionsPerHost int // maximum idle connections per host + ResponseHeaderTimeout int // amount of time (in seconds) to wait for server's response header + MaxChannelLength int // max number of allowed stat keys + PoolMaxWorkers int // max number of workers that will actually send the data to stats-server + PoolMaxCapacity int // number of tasks that can be hold by the pool +} diff --git a/modules/pubmatic/openwrap/metrics/stats/constants.go b/modules/pubmatic/openwrap/metrics/stats/constants.go new file mode 100644 index 00000000000..5ee89d2b822 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/stats/constants.go @@ -0,0 +1,225 @@ +package stats + +const ( + minPublishingInterval = 2 // In minutes + maxPublishingInterval = 5 // In minutes + minRetryDuration = 35 // In seconds + + minDialTimeout = 2 // In seconds + minKeepAliveDuration = 15 // In seconds + + contentType = "Content-Type" + applicationJSON = "application/json;charset=utf-8" + + minPublishingThreshold = 1000 + minResponseHeaderTimeout = 30 + minChannelLength = 1000 + minPoolWorker = 10 + minPoolCapacity = 1000 +) + +const ( + // ADD NEW STATS ID HERE + + //statsKeyOpenWrapServerPanic stats Key for Server Panic Hits + statsKeyOpenWrapServerPanic = iota + + //statsKeyNobidErrPrebidServerRequests stats Key to count Prebid Server Requests with No AdUnit for respective publisher + statsKeyNobidErrPrebidServerRequests + + //statsKeyNobidErrPrebidServerResponse stats Key to count requests with No bid from prebid server response for respective publisher + statsKeyNobidErrPrebidServerResponse + + //statsKeyContentObjectPresent for tracking the usage of content object in requests + statsKeyContentObjectPresent + + //statsKeyPublisherProfileRequests stats Key for Counting requests for a Profile Id for respective publisher + statsKeyPublisherProfileRequests + + //statsKeyPublisherInvProfileRequests stats Key for Counting requests with Invalid Profile Id for respective publisher + statsKeyPublisherInvProfileRequests + + //statsKeyPublisherInvProfileImpressions stats Key for Counting number of impressions lost in request with Invalid Profile Id for respective publisher + statsKeyPublisherInvProfileImpressions + + //statsKeyPrebidTORequests stats Key to count no of requests in which prebid timeouts + statsKeyPrebidTORequests + + //statsKeySsTORequests stats Key for Counting requests in which server side timeouts + statsKeySsTORequests + + //statsKeyNoUIDSErrorRequest stats Key for Counting requests with uids cookie not present + statsKeyNoUIDSErrorRequest + + //statsKeyVideoInterstitialImpressions stats Key for Counting video interstitial impressions for a publisher/profile + statsKeyVideoInterstitialImpressions + + //statsKeyVideoImpDisabledViaConfig stats Key for Counting video interstitial impressions that are disabled via config for a publisher/profile + statsKeyVideoImpDisabledViaConfig + + //statsKeyVideoImpDisabledViaConnType stats Key for Counting video interstitial impressions that are disabled because of connection type for a publisher/profile + statsKeyVideoImpDisabledViaConnType + + //statsKeyPublisherPartnerNoCookieRequests stats Key for counting requests without cookie at Publisher Partner level + statsKeyPublisherPartnerNoCookieRequests + + //statsKeySlotunMappedErrorRequests stats Key for counting Unmapped Slot impressions for respective Publisher Partner + statsKeySlotunMappedErrorRequests + + //statsKeyMisConfErrorRequests stats Key for counting missing configuration impressions for Publisher Partner + statsKeyMisConfErrorRequests + + //statsKeyPartnerTimeoutErrorRequests stats Key for counting Partner Timeout Requests for Parnter + statsKeyPartnerTimeoutErrorRequests + + //statsKeyUnknownPrebidErrorResponse stats Key for counting Unknown Error from Prebid Server for respective partner + statsKeyUnknownPrebidErrorResponse + + //statsKeyNobidErrorRequests stats Key for counting No Bid cases from respective partner + statsKeyNobidErrorRequests + + //statsKeyLoggerErrorRequests stats Key for counting number of Wrapper logger failures for a given publisher,profile and version + statsKeyLoggerErrorRequests + + //statsKey24PublisherRequests stats key to count no of 2.4 requests for a publisher + statsKey24PublisherRequests + + //statsKey25BadRequests stats key to count no of bad requests at 2.5 endpoint + statsKey25BadRequests + + //statsKey25PublisherRequests stats key to count no of 2.5 requests for a publisher + statsKey25PublisherRequests + + //statsKeyAMPBadRequests stats Key for counting number of AMP bad Requests + statsKeyAMPBadRequests + + //statsKeyAMPPublisherRequests stats Key for counting number of AMP Request for a publisher + statsKeyAMPPublisherRequests + + //statsKeyAMPCacheError stats Key for counting cache error for given pub and profile + statsKeyAMPCacheError + + //statsKeyPublisherInvProfileAMPRequests stats Key for Counting AMP requests with Invalid Profile Id for respective publisher + statsKeyPublisherInvProfileAMPRequests + + //statsKeyVideoBadRequests stats Key for counting number of Video Request + statsKeyVideoBadRequests + + //statsKeyVideoPublisherRequests stats Key for counting number of Video Request for a publisher + statsKeyVideoPublisherRequests + + //statsKeyVideoCacheError stats Key for counting cache error + statsKeyVideoCacheError + + //statsKeyPublisherInvProfileVideoRequests stats Key for Counting Video requests with Invalid Profile Id for respective publisher + statsKeyPublisherInvProfileVideoRequests + + //statsKeyInvalidCreatives stats Key for counting invalid creatives for Publisher Partner + statsKeyInvalidCreatives + + //statsKeyPlatformPublisherPartnerRequests stats Key for counting Platform Publisher Partner level Requests + statsKeyPlatformPublisherPartnerRequests + + //statsKeyPlatformPublisherPartnerResponses stats Key for counting Platform Publisher Partner level Responses + statsKeyPlatformPublisherPartnerResponses + + //statsKeyPublisherResponseEncodingErrors stats Key to count errors during response encoding at Publisher level + statsKeyPublisherResponseEncodingErrors + + //Bucketwise latency related Stats Keys for publisher partner level for response time + + //statsKeyA2000 response time above 2000ms + statsKeyA2000 + //statsKeyA1500 response time between 1500ms and 2000ms + statsKeyA1500 + //statsKeyA1000 response time between 1000ms and 1500ms + statsKeyA1000 + //statsKeyA900 response time between 900ms and 1000ms + statsKeyA900 + //statsKeyA800 response time between 800ms and 900ms + statsKeyA800 + //statsKeyA700 response time between 700ms and 800ms + statsKeyA700 + //statsKeyA600 response time between 600ms and 700ms + statsKeyA600 + //statsKeyA500 response time between 500ms and 600ms + statsKeyA500 + //statsKeyA400 response time between 400ms and 500ms + statsKeyA400 + //statsKeyA300 response time between 300ms and 400ms + statsKeyA300 + //statsKeyA200 response time between 200ms and 300ms + statsKeyA200 + //statsKeyA100 response time between 100ms and 200ms + statsKeyA100 + //statsKeyA50 response time between 50ms and 100ms + statsKeyA50 + //statsKeyL50 response time less than 50ms + statsKeyL50 + + //Bucketwise latency related Stats Keys for a publisher for pre-processing time + + //statsKeyPrTimeAbv100 bucket for pre processing time above 100ms + statsKeyPrTimeAbv100 + //statsKeyPrTimeAbv50 bucket for pre processing time bw 50ms anb 100ms + statsKeyPrTimeAbv50 + //statsKeyPrTimeAbv10 bucket for pre processing time bw 10ms anb 50ms + statsKeyPrTimeAbv10 + //statsKeyPrTimeAbv1 bucket for pre processing time bw 1ms anb 10ms + statsKeyPrTimeAbv1 + //statsKeyPrTimeBlw1 bucket for pre processing time below 1ms + statsKeyPrTimeBlw1 + + //statsKeyBannerImpDisabledViaConfig stats Key for Counting banner impressions that are disabled via config for a publisher/profile + statsKeyBannerImpDisabledViaConfig + + // ********************* CTV Stats ********************* + + //statsKeyCTVPrebidFailedImpression for counting number of CTV prebid side failed impressions + statsKeyCTVPrebidFailedImpression + //statsKeyCTVRequests for counting number of CTV Requests + statsKeyCTVRequests + //statsKeyCTVBadRequests for counting number of CTV Bad Requests + statsKeyCTVBadRequests + //statsKeyCTVPublisherRequests for counting number of CTV Publisher Requests + statsKeyCTVPublisherRequests + //statsKeyCTVHTTPMethodRequests for counting number of CTV Publisher GET/POST Requests + statsKeyCTVHTTPMethodRequests + //statsKeyCTVValidationDetail for tracking error with granularity + statsKeyCTVValidationErr + //statsKeyCTVReqImpstWithConfig for tracking requests that had config and were not overwritten by database config + statsKeyCTVReqImpstWithConfig + //statsKeyTotalAdPodImpression for tracking no of AdPod impressions + statsKeyTotalAdPodImpression + //statsKeyAdPodSecondsMissed for tracking no pf seconds that were missed because of our algos + statsKeyReqTotalAdPodImpression + //statsKeyReqImpDurationYield is for tracking the number on adpod impressions generated for give min and max request imp durations + statsKeyReqImpDurationYield + //statsKeyReqWithAdPodCount if for counting requests with AdPods + statsKeyReqWithAdPodCount + //statsKeyPBSAuctionRequests stats Key for counting PBS Auction endpoint Requests + statsKeyPBSAuctionRequests + + //statsKeyInjectTrackerErrorCount stats key for counting error during injecting tracker in Creative + statsKeyInjectTrackerErrorCount + + //statsBidResponsesByDealUsingPBS stats key for counting number of bids received which for given deal id, profile id, publisherid + statsBidResponsesByDealUsingPBS + + //statsBidResponsesByDealUsingHB stats key for counting number of bids received which for given deal id, profile id, publisherid + statsBidResponsesByDealUsingHB + + // statsPartnerTimeoutInPBS stats key for countiing number of timeouts occured for given publisher and profile + statsPartnerTimeoutInPBS + + // This is to declare the array of stats, add new stats above this + maxNumOfStats + // NOTE - DON'T ADD NEW STATS KEY BELOW THIS. NEW STATS SHOULD BE ADDED ABOVE maxNumOfStats +) + +// constant to defines status-code used while sending stats to server +const ( + statusSetupFail = iota + statusPublishSuccess + statusPublishFail +) diff --git a/modules/pubmatic/openwrap/metrics/stats/init.go b/modules/pubmatic/openwrap/metrics/stats/init.go new file mode 100644 index 00000000000..e963e6f3df0 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/stats/init.go @@ -0,0 +1,265 @@ +package stats + +import ( + "sync" +) + +type statKeyName = string + +var ( + statKeys [maxNumOfStats]statKeyName + once sync.Once + owStats *StatsTCP + owStatsErr error +) + +// stat represents a single stat-key along with its value +type stat struct { + Key string + Value int +} + +// InitStatsClient initializes stats client +func InitStatsClient(endpoint, defaultHost, actualHost, dcName string, + pubInterval, pubThreshold, retries, dialTimeout, keepAliveDuration, + maxIdleConnes, maxIdleConnesPerHost, respHeaderTimeout, maxChannelLength, + poolMaxWorkers, poolMaxCapacity int) (*StatsTCP, error) { + + once.Do(func() { + initStatKeys(dcName+":"+defaultHost, dcName+":"+actualHost) + owStats, owStatsErr = initTCPStatsClient(endpoint, pubInterval, pubThreshold, + retries, dialTimeout, keepAliveDuration, maxIdleConnes, maxIdleConnesPerHost, + respHeaderTimeout, maxChannelLength, poolMaxWorkers, poolMaxCapacity) + }) + + return owStats, owStatsErr +} + +// initStatKeys sets the key-name for all stats +// defaultServerName will be "actualDCName:N:P" +// actualServerName will be "actualDCName:actualNode:actualPod" +func initStatKeys(defaultServerName, actualServerName string) { + + //server level stats + statKeys[statsKeyOpenWrapServerPanic] = "hb:panic:" + actualServerName + //hb:panic: + + // statKeys[statsKeyNobidErrPrebidServerRequests] = "hb:pubnbreq:%s:", SendThresh: criticalThreshold, SendTimeInterval: time.Minute * time.Duration(criticalInterval)} + statKeys[statsKeyNobidErrPrebidServerRequests] = "hb:pubnbreq:%s:" + defaultServerName + //hb:pubnbreq:: + + statKeys[statsKeyNobidErrPrebidServerResponse] = "hb:pubnbres:%s:" + defaultServerName + //hb:pubnbres:: + + statKeys[statsKeyContentObjectPresent] = "hb:cnt:%s:%s:" + defaultServerName + //hb:cnt::: + + //publisher and profile level stats + statKeys[statsKeyPublisherProfileRequests] = "hb:pprofrq:%s:%s:" + defaultServerName + //hb:pprofrq::: + + statKeys[statsKeyPublisherInvProfileRequests] = "hb:pubinp:%s:%s:" + defaultServerName + //hb:pubinp::: + + statKeys[statsKeyPublisherInvProfileImpressions] = "hb:pubinpimp:%s:%s:" + defaultServerName + //hb:pubinpimp::: + + statKeys[statsKeyPrebidTORequests] = "hb:prebidto:%s:%s:" + defaultServerName + //hb:prebidto::: + + statKeys[statsKeySsTORequests] = "hb:ssto:%s:%s:" + defaultServerName + //hb:ssto::: + + statKeys[statsKeyNoUIDSErrorRequest] = "hb:nouids:%s:%s:" + defaultServerName + //hb:nouids::: + + statKeys[statsKeyVideoInterstitialImpressions] = "hb:ppvidinstlimps:%s:%s:" + defaultServerName + //hb:ppvidinstlimps::: + + statKeys[statsKeyVideoImpDisabledViaConfig] = "hb:ppdisimpcfg:%s:%s:" + defaultServerName + //hb:ppdisimpcfg::: + + statKeys[statsKeyVideoImpDisabledViaConnType] = "hb:ppdisimpct:%s:%s:" + defaultServerName + //hb:ppdisimpct::: + + statKeys[statsKeyPublisherPartnerNoCookieRequests] = "hb:ppnc:%s:%s:" + defaultServerName + //hb:ppnc::: + + statKeys[statsKeySlotunMappedErrorRequests] = "hb:sler:%s:%s:" + defaultServerName + //hb:sler::: + + statKeys[statsKeyMisConfErrorRequests] = "hb:cfer:%s:%s:" + defaultServerName + //hb:cfer::: + + statKeys[statsKeyPartnerTimeoutErrorRequests] = "hb:toer:%s:%s:" + defaultServerName + //hb:toer::: + + statKeys[statsKeyUnknownPrebidErrorResponse] = "hb:uner:%s:%s:" + defaultServerName + //hb:uner::: + + statKeys[statsKeyNobidErrorRequests] = "hb:nber:%s:%s:" + defaultServerName + //hb:nber::: + + statKeys[statsKeyLoggerErrorRequests] = "hb:wle:%s:%s:%s:" + defaultServerName + //hb:nber:::: + + statKeys[statsKey24PublisherRequests] = "hb:2.4:%s:pbrq:%s:" + defaultServerName + //hb:2.4::pbrq:: + + statKeys[statsKey25BadRequests] = "hb:2.5:badreq:" + defaultServerName + //hb:2.5:badreq: + + statKeys[statsKey25PublisherRequests] = "hb:2.5:%s:pbrq:%s:" + defaultServerName + //hb:2.5::pbrq:: + + statKeys[statsKeyAMPBadRequests] = "hb:amp:badreq:" + defaultServerName + //hb:amp:badreq: + + statKeys[statsKeyAMPPublisherRequests] = "hb:amp:pbrq:%s:" + defaultServerName + //hb:amp:pbrq:: + + statKeys[statsKeyAMPCacheError] = "hb:amp:ce:%s:%s:" + defaultServerName + //hb:amp:ce::: + + statKeys[statsKeyPublisherInvProfileAMPRequests] = "hb:amp:pubinp:%s:%s:" + defaultServerName + //hb:amp:pubinp::: + + statKeys[statsKeyVideoBadRequests] = "hb:vid:badreq:" + defaultServerName + //hb:vid:badreq: + + statKeys[statsKeyVideoPublisherRequests] = "hb:vid:pbrq:%s:" + defaultServerName + //hb:vid:pbrq:: + + statKeys[statsKeyVideoCacheError] = "hb:vid:ce:%s:%s:" + defaultServerName + //hb:vid:ce::: + + statKeys[statsKeyPublisherInvProfileVideoRequests] = "hb:vid:pubinp:%s:%s:" + defaultServerName + //hb:vid:pubinp::: + + statKeys[statsKeyInvalidCreatives] = "hb:invcr:%s:%s:" + defaultServerName + //hb:invcr::: + + statKeys[statsKeyPlatformPublisherPartnerRequests] = "hb:pppreq:%s:%s:%s:" + defaultServerName + //hb:pppreq:::: + + statKeys[statsKeyPlatformPublisherPartnerResponses] = "hb:pppres:%s:%s:%s:" + defaultServerName + //hb:pppres:::: + + statKeys[statsKeyPublisherResponseEncodingErrors] = "hb:encerr:%s:" + defaultServerName + //hb:vid:encerr:: + + statKeys[statsKeyA2000] = "hb:latabv_2000:%s:%s:" + defaultServerName + //hb:latabv_2000::: + + statKeys[statsKeyA1500] = "hb:latabv_1500:%s:%s:" + defaultServerName + //hb:latabv_1500::: + + statKeys[statsKeyA1000] = "hb:latabv_1000:%s:%s:" + defaultServerName + //hb:latabv_1000::: + + statKeys[statsKeyA900] = "hb:latabv_900:%s:%s:" + defaultServerName + //hb:latabv_900::: + + statKeys[statsKeyA800] = "hb:latabv_800:%s:%s:" + defaultServerName + //hb:latabv_800::: + + // TBD : @viral key-change ??? + // statKeys[statsKeyA800] = statsclient.Stats{Fmt: "hb:latabv_800:%s:%s:%s", SendThresh: standardThreshold, SendTimeInterval: time.Minute * time.Duration(standardInterval)} + //hb:latabv_800::: + // statKeys[statsKeyA700] = statsclient.Stats{Fmt: "hb:latabv_800:%s:%s:%s", SendThresh: standardThreshold, SendTimeInterval: time.Minute * time.Duration(standardInterval)} + //hb:latabv_700::: + statKeys[statsKeyA700] = "hb:latabv_700:%s:%s:" + defaultServerName + //hb:latabv_700::: + + statKeys[statsKeyA600] = "hb:latabv_600:%s:%s:" + defaultServerName + //hb:latabv_600::: + + statKeys[statsKeyA500] = "hb:latabv_500:%s:%s:" + defaultServerName + //hb:latabv_500::: + + statKeys[statsKeyA400] = "hb:latabv_400:%s:%s:" + defaultServerName + //hb:latabv_400::: + + statKeys[statsKeyA300] = "hb:latabv_300:%s:%s:" + defaultServerName + //hb:latabv_300::: + + statKeys[statsKeyA200] = "hb:latabv_200:%s:%s:" + defaultServerName + //hb:latabv_200::: + + statKeys[statsKeyA100] = "hb:latabv_100:%s:%s:" + defaultServerName + //hb:latabv_100::: + + statKeys[statsKeyA50] = "hb:latabv_50:%s:%s:" + defaultServerName + //hb:latabv_50::: + + statKeys[statsKeyL50] = "hb:latblw_50:%s:%s:" + defaultServerName + //hb:latblw_50::: + + statKeys[statsKeyPrTimeAbv100] = "hb:ptabv_100:%s:" + defaultServerName + //hb:ptabv_100:: + + statKeys[statsKeyPrTimeAbv50] = "hb:ptabv_50:%s:" + defaultServerName + //hb:ptabv_50:: + + statKeys[statsKeyPrTimeAbv10] = "hb:ptabv_10:%s:" + defaultServerName + //hb:ptabv_10:: + + statKeys[statsKeyPrTimeAbv1] = "hb:ptabv_1:%s:" + defaultServerName + //hb:ptabv_1:: + + statKeys[statsKeyPrTimeBlw1] = "hb:ptblw_1:%s:" + defaultServerName + //hb:ptblw_1:: + + statKeys[statsKeyBannerImpDisabledViaConfig] = "hb:bnrdiscfg:%s:%s:" + defaultServerName + //hb:bnrdiscfg::: + + //CTV Specific Keys + + statKeys[statsKeyCTVPrebidFailedImpression] = "hb:lfv:badimp:%v:%v:%v:" + defaultServerName + //hb:lfv:badimp:::: + + statKeys[statsKeyCTVRequests] = "hb:lfv:%v:%v:req:" + defaultServerName + //hb:lfv:::req: + + statKeys[statsKeyCTVBadRequests] = "hb:lfv:%v:badreq:%d:" + defaultServerName + //hb:lfv::badreq:: + + statKeys[statsKeyCTVPublisherRequests] = "hb:lfv:%v:%v:pbrq:%v:" + defaultServerName + //hb:lfv:::pbrq:: + + statKeys[statsKeyCTVHTTPMethodRequests] = "hb:lfv:%v:mtd:%v:%v:" + defaultServerName + //hb:lfv::mtd::: + + statKeys[statsKeyCTVValidationErr] = "hb:lfv:ivr:%d:%s:" + defaultServerName + //hb:lfv:ivr::: + + statKeys[statsKeyCTVReqImpstWithConfig] = "hb:lfv:rwc:%s:%s:" + defaultServerName + //hb:lfv:rwc::: + + statKeys[statsKeyTotalAdPodImpression] = "hb:lfv:tpi:%s:%s:" + defaultServerName + //hb:lfv:tpi::: + + statKeys[statsKeyReqTotalAdPodImpression] = "hb:lfv:rtpi:%s:" + defaultServerName + //hb:lfv:rtpi:: + + statKeys[statsKeyReqImpDurationYield] = "hb:lfv:impy:%d:%d:%s:" + defaultServerName + //hb:lfv:impy:::: + + statKeys[statsKeyReqWithAdPodCount] = "hb:lfv:rwap:%s:%s:" + defaultServerName + //hb:lfv:rwap::: + + statKeys[statsKeyPBSAuctionRequests] = "hb:pbs:auc:" + defaultServerName + //hb:pbs:auc: - no of PBS auction endpoint requests + + statKeys[statsKeyInjectTrackerErrorCount] = "hb:mistrack:%s:%s:%s:" + defaultServerName + //hb:mistrack:::: - Error during Injecting Tracker + + statKeys[statsBidResponsesByDealUsingPBS] = "hb:pbs:dbc:%s:%s:%s:%s:" + defaultServerName + //hb:pbs:dbc::::: - PubMatic-OpenWrap to count number of responses received from aliasbidder per publisher profile + + statKeys[statsBidResponsesByDealUsingHB] = "hb:dbc:%s:%s:%s:%s:" + defaultServerName + //hb:dbc::::: - header-bidding to count number of responses received from aliasbidder per publisher profile + + statKeys[statsPartnerTimeoutInPBS] = "hb:pbs:pto:%s:%s:%s:" + defaultServerName + //hb:pbs:pto:::: - count timeout by aliasbidder per publisher profile +} diff --git a/modules/pubmatic/openwrap/metrics/stats/init_test.go b/modules/pubmatic/openwrap/metrics/stats/init_test.go new file mode 100644 index 00000000000..92aea299b25 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/stats/init_test.go @@ -0,0 +1,175 @@ +package stats + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInitStatKeys(t *testing.T) { + + type args struct { + defaultServerName, actualServerName string + } + + type want struct { + testKeys [maxNumOfStats]string + } + + tests := []struct { + name string + args args + want want + }{ + { + name: "test_init_stat_keys", + args: args{ + defaultServerName: "sv3:N:P", + actualServerName: "sv3:node123.sv3:ssheaderbidding", + }, + want: want{ + testKeys: [maxNumOfStats]string{ + "hb:panic:sv3:node123.sv3:ssheaderbidding", + "hb:pubnbreq:%s:sv3:N:P", + "hb:pubnbres:%s:sv3:N:P", + "hb:cnt:%s:%s:sv3:N:P", + "hb:pprofrq:%s:%s:sv3:N:P", + "hb:pubinp:%s:%s:sv3:N:P", + "hb:pubinpimp:%s:%s:sv3:N:P", + "hb:prebidto:%s:%s:sv3:N:P", + "hb:ssto:%s:%s:sv3:N:P", + "hb:nouids:%s:%s:sv3:N:P", + "hb:ppvidinstlimps:%s:%s:sv3:N:P", + "hb:ppdisimpcfg:%s:%s:sv3:N:P", + "hb:ppdisimpct:%s:%s:sv3:N:P", + "hb:ppnc:%s:%s:sv3:N:P", + "hb:sler:%s:%s:sv3:N:P", + "hb:cfer:%s:%s:sv3:N:P", + "hb:toer:%s:%s:sv3:N:P", + "hb:uner:%s:%s:sv3:N:P", + "hb:nber:%s:%s:sv3:N:P", + "hb:wle:%s:%s:%s:sv3:N:P", + "hb:2.4:%s:pbrq:%s:sv3:N:P", + "hb:2.5:badreq:sv3:N:P", + "hb:2.5:%s:pbrq:%s:sv3:N:P", + "hb:amp:badreq:sv3:N:P", + "hb:amp:pbrq:%s:sv3:N:P", + "hb:amp:ce:%s:%s:sv3:N:P", + "hb:amp:pubinp:%s:%s:sv3:N:P", + "hb:vid:badreq:sv3:N:P", + "hb:vid:pbrq:%s:sv3:N:P", + "hb:vid:ce:%s:%s:sv3:N:P", + "hb:vid:pubinp:%s:%s:sv3:N:P", + "hb:invcr:%s:%s:sv3:N:P", + "hb:pppreq:%s:%s:%s:sv3:N:P", + "hb:pppres:%s:%s:%s:sv3:N:P", + "hb:encerr:%s:sv3:N:P", + "hb:latabv_2000:%s:%s:sv3:N:P", + "hb:latabv_1500:%s:%s:sv3:N:P", + "hb:latabv_1000:%s:%s:sv3:N:P", + "hb:latabv_900:%s:%s:sv3:N:P", + "hb:latabv_800:%s:%s:sv3:N:P", + "hb:latabv_700:%s:%s:sv3:N:P", + "hb:latabv_600:%s:%s:sv3:N:P", + "hb:latabv_500:%s:%s:sv3:N:P", + "hb:latabv_400:%s:%s:sv3:N:P", + "hb:latabv_300:%s:%s:sv3:N:P", + "hb:latabv_200:%s:%s:sv3:N:P", + "hb:latabv_100:%s:%s:sv3:N:P", + "hb:latabv_50:%s:%s:sv3:N:P", + "hb:latblw_50:%s:%s:sv3:N:P", + "hb:ptabv_100:%s:sv3:N:P", + "hb:ptabv_50:%s:sv3:N:P", + "hb:ptabv_10:%s:sv3:N:P", + "hb:ptabv_1:%s:sv3:N:P", + "hb:ptblw_1:%s:sv3:N:P", + "hb:bnrdiscfg:%s:%s:sv3:N:P", + "hb:lfv:badimp:%v:%v:%v:sv3:N:P", + "hb:lfv:%v:%v:req:sv3:N:P", + "hb:lfv:%v:badreq:%d:sv3:N:P", + "hb:lfv:%v:%v:pbrq:%v:sv3:N:P", + "hb:lfv:%v:mtd:%v:%v:sv3:N:P", + "hb:lfv:ivr:%d:%s:sv3:N:P", + "hb:lfv:rwc:%s:%s:sv3:N:P", + "hb:lfv:tpi:%s:%s:sv3:N:P", + "hb:lfv:rtpi:%s:sv3:N:P", + "hb:lfv:impy:%d:%d:%s:sv3:N:P", + "hb:lfv:rwap:%s:%s:sv3:N:P", + "hb:pbs:auc:sv3:N:P", + "hb:mistrack:%s:%s:%s:sv3:N:P", + "hb:pbs:dbc:%s:%s:%s:%s:sv3:N:P", + "hb:dbc:%s:%s:%s:%s:sv3:N:P", + "hb:pbs:pto:%s:%s:%s:sv3:N:P", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + initStatKeys(tt.args.defaultServerName, tt.args.actualServerName) + assert.Equal(t, statKeys, tt.want.testKeys) + }) + } +} + +func TestInitStat(t *testing.T) { + + type args struct { + endpoint, defaultHost, actualHost, dcName string + pubInterval, pubThreshold, retries, dialTimeout, keepAliveDuration, + maxIdleConnes, maxIdleConnesPerHost, respHeaderTimeout, maxChannelLength, + poolMaxWorkers, poolMaxCapacity int + } + + type want struct { + client *StatsTCP + err error + } + + tests := []struct { + name string + args args + want want + setup func() want + }{ + { + name: "singleton_instance", + args: args{ + endpoint: "10.10.10.10", + defaultHost: "N:P", + actualHost: "node1.sv3:ssheader", + dcName: "sv3", + pubInterval: 10, + pubThreshold: 10, + retries: 3, + dialTimeout: 10, + keepAliveDuration: 10, + maxIdleConnes: 10, + maxIdleConnesPerHost: 10, + respHeaderTimeout: 10, + maxChannelLength: 10, + poolMaxWorkers: 10, + poolMaxCapacity: 10, + }, + setup: func() want { + st, err := InitStatsClient("10.10.10.10/stats", "N:P", "node1.sv3:ssheader", "sv3", 10, 10, 3, 10, 10, 10, 10, 10, 10, 10, 10) + return want{client: st, err: err} + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.want = tt.setup() + + InitStatsClient(tt.args.endpoint, tt.args.defaultHost, tt.args.actualHost, tt.args.dcName, + tt.args.pubInterval, tt.args.pubThreshold, tt.args.retries, tt.args.dialTimeout, tt.args.keepAliveDuration, + tt.args.maxIdleConnes, tt.args.maxIdleConnesPerHost, tt.args.respHeaderTimeout, + tt.args.maxChannelLength, tt.args.poolMaxWorkers, tt.args.poolMaxCapacity) + + assert.Equal(t, tt.want.client, owStats) + assert.Equal(t, tt.want.err, owStatsErr) + }) + } +} diff --git a/modules/pubmatic/openwrap/metrics/stats/mock/mock.go b/modules/pubmatic/openwrap/metrics/stats/mock/mock.go new file mode 100644 index 00000000000..2fbbe2dde6b --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/stats/mock/mock.go @@ -0,0 +1,86 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/PubMatic-OpenWrap/prebid-server/modules/pubmatic/openwrap/metrics/stats (interfaces: HttpClient,WorkerPool) + +// Package mock_stats is a generated GoMock package. +package mock + +import ( + gomock "github.com/golang/mock/gomock" + http "net/http" + reflect "reflect" +) + +// MockHttpClient is a mock of HttpClient interface +type MockHttpClient struct { + ctrl *gomock.Controller + recorder *MockHttpClientMockRecorder +} + +// MockHttpClientMockRecorder is the mock recorder for MockHttpClient +type MockHttpClientMockRecorder struct { + mock *MockHttpClient +} + +// NewMockHttpClient creates a new mock instance +func NewMockHttpClient(ctrl *gomock.Controller) *MockHttpClient { + mock := &MockHttpClient{ctrl: ctrl} + mock.recorder = &MockHttpClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockHttpClient) EXPECT() *MockHttpClientMockRecorder { + return m.recorder +} + +// Do mocks base method +func (m *MockHttpClient) Do(arg0 *http.Request) (*http.Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Do", arg0) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Do indicates an expected call of Do +func (mr *MockHttpClientMockRecorder) Do(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockHttpClient)(nil).Do), arg0) +} + +// MockWorkerPool is a mock of WorkerPool interface +type MockWorkerPool struct { + ctrl *gomock.Controller + recorder *MockWorkerPoolMockRecorder +} + +// MockWorkerPoolMockRecorder is the mock recorder for MockWorkerPool +type MockWorkerPoolMockRecorder struct { + mock *MockWorkerPool +} + +// NewMockWorkerPool creates a new mock instance +func NewMockWorkerPool(ctrl *gomock.Controller) *MockWorkerPool { + mock := &MockWorkerPool{ctrl: ctrl} + mock.recorder = &MockWorkerPoolMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockWorkerPool) EXPECT() *MockWorkerPoolMockRecorder { + return m.recorder +} + +// TrySubmit mocks base method +func (m *MockWorkerPool) TrySubmit(arg0 func()) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TrySubmit", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// TrySubmit indicates an expected call of TrySubmit +func (mr *MockWorkerPoolMockRecorder) TrySubmit(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TrySubmit", reflect.TypeOf((*MockWorkerPool)(nil).TrySubmit), arg0) +} diff --git a/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go b/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go new file mode 100644 index 00000000000..2fe86bada38 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go @@ -0,0 +1,341 @@ +package stats + +import ( + "fmt" + "time" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +type StatsTCP struct { + statsClient *Client +} + +func initTCPStatsClient(endpoint string, + pubInterval, pubThreshold, retries, dialTimeout, keepAliveDur, maxIdleConn, + maxIdleConnPerHost, respHeaderTimeout, maxChannelLength, poolMaxWorkers, poolMaxCapacity int) (*StatsTCP, error) { + + cfg := config{ + Endpoint: endpoint, + PublishingInterval: pubInterval, + PublishingThreshold: pubThreshold, + Retries: retries, + DialTimeout: dialTimeout, + KeepAliveDuration: keepAliveDur, + MaxIdleConns: maxIdleConn, + MaxIdleConnsPerHost: maxIdleConnPerHost, + ResponseHeaderTimeout: respHeaderTimeout, + MaxChannelLength: maxChannelLength, + PoolMaxWorkers: poolMaxWorkers, + PoolMaxCapacity: poolMaxCapacity, + } + + sc, err := NewClient(&cfg) + if err != nil { + glog.Errorf("[stats_fail] Failed to initialize stats client : %v", err.Error()) + return nil, err + } + + return &StatsTCP{statsClient: sc}, nil +} + +func (st *StatsTCP) RecordOpenWrapServerPanicStats(host, method string) { + st.statsClient.PublishStat(statKeys[statsKeyOpenWrapServerPanic], 1) +} + +func (st *StatsTCP) RecordPublisherPartnerNoCookieStats(publisher, partner string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyPublisherPartnerNoCookieRequests], publisher, partner), 1) +} + +func (st *StatsTCP) RecordPartnerResponseErrors(publisher, partner, err string) { + switch err { + case models.PartnerErrTimeout: + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyPartnerTimeoutErrorRequests], publisher, partner), 1) + case models.PartnerErrNoBid: + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyNobidErrorRequests], publisher, partner), 1) + case models.PartnerErrUnknownPrebidError: + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyUnknownPrebidErrorResponse], publisher, partner), 1) + } +} + +func (st *StatsTCP) RecordPartnerConfigErrors(publisher, profile, partner string, errcode int) { + switch errcode { + case models.PartnerErrMisConfig: + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyMisConfErrorRequests], publisher, partner), 1) + case models.PartnerErrSlotNotMapped: + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeySlotunMappedErrorRequests], publisher, partner), 1) + } +} + +func (st *StatsTCP) RecordPublisherProfileRequests(publisher, profileID string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyPublisherProfileRequests], publisher, profileID), 1) +} + +func (st *StatsTCP) RecordPublisherInvalidProfileRequests(endpoint, publisher, profileID string) { + switch endpoint { + case "video": + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyPublisherInvProfileVideoRequests], publisher, profileID), 1) + case "amp": + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyPublisherInvProfileAMPRequests], publisher, profileID), 1) + default: + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyPublisherInvProfileRequests], publisher, profileID), 1) + } +} + +func (st *StatsTCP) RecordPublisherInvalidProfileImpressions(publisher, profileID string, impCount int) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyPublisherInvProfileImpressions], publisher, profileID), impCount) + //TODO @viral ;previously by 1 but now by impCount +} + +func (st *StatsTCP) RecordNobidErrPrebidServerRequests(publisher string, nbr int) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyNobidErrPrebidServerRequests], publisher), 1) +} + +func (st *StatsTCP) RecordNobidErrPrebidServerResponse(publisher string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyNobidErrPrebidServerResponse], publisher), 1) +} + +func (st *StatsTCP) RecordInvalidCreativeStats(publisher, partner string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyInvalidCreatives], publisher, partner), 1) +} + +func (st *StatsTCP) RecordPlatformPublisherPartnerReqStats(platform, publisher, partner string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyPlatformPublisherPartnerRequests], platform, publisher, partner), 1) +} + +func (st *StatsTCP) RecordPlatformPublisherPartnerResponseStats(platform, publisher, partner string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyPlatformPublisherPartnerResponses], platform, publisher, partner), 1) +} + +func (st *StatsTCP) RecordPublisherResponseEncodingErrorStats(publisher string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyPublisherResponseEncodingErrors], publisher), 1) +} + +func (st *StatsTCP) RecordPartnerResponseTimeStats(publisher, partner string, responseTime int) { + statKeyIndex := getStatsKeyIndexForResponseTime(responseTime) + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statKeyIndex], publisher, partner), 1) +} + +func (st *StatsTCP) RecordPublisherResponseTimeStats(publisher string, responseTime int) { + statKeyIndex := getStatsKeyIndexForResponseTime(responseTime) + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statKeyIndex], publisher, "overall"), 1) +} + +func (st *StatsTCP) RecordPublisherWrapperLoggerFailure(publisher, profileID, versionID string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyLoggerErrorRequests], publisher, profileID, versionID), 1) +} + +func (st *StatsTCP) RecordPrebidTimeoutRequests(publisher, profileID string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyPrebidTORequests], publisher, profileID), 1) +} + +func (st *StatsTCP) RecordSSTimeoutRequests(publisher, profileID string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeySsTORequests], publisher, profileID), 1) +} + +func (st *StatsTCP) RecordUidsCookieNotPresentErrorStats(publisher, profileID string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyNoUIDSErrorRequest], publisher, profileID), 1) +} + +func (st *StatsTCP) RecordVideoInstlImpsStats(publisher, profileID string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyVideoInterstitialImpressions], publisher, profileID), 1) +} + +func (st *StatsTCP) RecordImpDisabledViaConfigStats(impType, publisher, profileID string) { + switch impType { + case "video": + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyVideoImpDisabledViaConfig], publisher, profileID), 1) + case "banner": + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyBannerImpDisabledViaConfig], publisher, profileID), 1) + } +} + +func (st *StatsTCP) RecordVideoImpDisabledViaConnTypeStats(publisher, profileID string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyVideoImpDisabledViaConnType], publisher, profileID), 1) +} + +func (st *StatsTCP) RecordPreProcessingTimeStats(publisher string, processingTime int) { + statKeyIndex := 0 + switch { + case processingTime >= 100: + statKeyIndex = statsKeyPrTimeAbv100 + case processingTime >= 50: + statKeyIndex = statsKeyPrTimeAbv50 + case processingTime >= 10: + statKeyIndex = statsKeyPrTimeAbv10 + case processingTime >= 1: + statKeyIndex = statsKeyPrTimeAbv1 + default: // below 1ms + statKeyIndex = statsKeyPrTimeBlw1 + } + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statKeyIndex], publisher), 1) +} + +func (st *StatsTCP) RecordStatsKeyCTVPrebidFailedImpression(errorcode int, publisher string, profile string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyCTVPrebidFailedImpression], errorcode, publisher, profile), 1) +} + +func (st *StatsTCP) RecordCTVRequests(endpoint string, platform string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyCTVRequests], endpoint, platform), 1) +} + +func (st *StatsTCP) RecordBadRequests(endpoint string, errorCode int) { + switch endpoint { + case "amp": + st.statsClient.PublishStat(statKeys[statsKeyAMPBadRequests], 1) + case "video": + st.statsClient.PublishStat(statKeys[statsKeyVideoBadRequests], 1) + case "v25": + st.statsClient.PublishStat(statKeys[statsKey25BadRequests], 1) + case "vast", "ortb", "json", "openwrap": + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyCTVBadRequests], endpoint, errorCode), 1) + } +} + +func (st *StatsTCP) RecordCTVHTTPMethodRequests(endpoint string, publisher string, method string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyCTVHTTPMethodRequests], endpoint, publisher, method), 1) +} + +func (st *StatsTCP) RecordCTVInvalidReasonCount(errorCode int, publisher string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyCTVValidationErr], errorCode, publisher), 1) +} + +func (st *StatsTCP) RecordCTVReqImpsWithDbConfigCount(publisher string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyCTVReqImpstWithConfig], "db", publisher), 1) +} + +func (st *StatsTCP) RecordCTVReqImpsWithReqConfigCount(publisher string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyCTVReqImpstWithConfig], "req", publisher), 1) +} + +func (st *StatsTCP) RecordAdPodGeneratedImpressionsCount(impCount int, publisher string) { + var impRange string + if impCount <= 3 { + impRange = "1-3" + } else if impCount <= 6 { + impRange = "4-6" + } else if impCount <= 9 { + impRange = "7-9" + } else { + impRange = "9+" + } + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyTotalAdPodImpression], impRange, publisher), 1) +} + +func (st *StatsTCP) RecordRequestAdPodGeneratedImpressionsCount(impCount int, publisher string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyReqTotalAdPodImpression], publisher), impCount) +} + +func (st *StatsTCP) RecordReqImpsWithContentCount(publisher, contentType string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyContentObjectPresent], contentType, publisher), 1) +} + +func (st *StatsTCP) RecordAdPodImpressionYield(maxDuration int, minDuration int, publisher string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyReqImpDurationYield], maxDuration, minDuration, publisher), 1) +} + +func (st *StatsTCP) RecordCTVReqCountWithAdPod(publisherID, profileID string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyReqWithAdPodCount], publisherID, profileID), 1) +} + +func (st *StatsTCP) RecordPBSAuctionRequestsStats() { + st.statsClient.PublishStat(statKeys[statsKeyPBSAuctionRequests], 1) +} + +func (st *StatsTCP) RecordInjectTrackerErrorCount(adformat, publisher, partner string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyInjectTrackerErrorCount], adformat, publisher, partner), 1) +} + +func (st *StatsTCP) RecordBidResponseByDealCountInPBS(publisher, profile, aliasBidder, dealId string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsBidResponsesByDealUsingPBS], publisher, profile, aliasBidder, dealId), 1) +} + +func (st *StatsTCP) RecordBidResponseByDealCountInHB(publisher, profile, aliasBidder, dealId string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsBidResponsesByDealUsingHB], publisher, profile, aliasBidder, dealId), 1) +} + +func (st *StatsTCP) RecordPartnerTimeoutInPBS(publisher, profile, aliasBidder string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsPartnerTimeoutInPBS], publisher, profile, aliasBidder), 1) +} + +func (st *StatsTCP) RecordPublisherRequests(endpoint, publisher, platform string) { + + if platform == models.PLATFORM_APP { + platform = models.HB_PLATFORM_APP + } + switch endpoint { + case "amp": + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyAMPPublisherRequests], publisher), 1) + case "video": + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyVideoPublisherRequests], publisher), 1) + case "v25": + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKey25PublisherRequests], platform, publisher), 1) + case "vast", "ortb", "json": + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyCTVPublisherRequests], endpoint, platform, publisher), 1) + } +} + +func (st *StatsTCP) RecordCacheErrorRequests(endpoint, publisher, profileID string) { + switch endpoint { + case "amp": + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyAMPCacheError], publisher, profileID), 1) + case "video": + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyVideoCacheError], publisher, profileID), 1) + } +} + +func (st *StatsTCP) RecordGetProfileDataTime(requestType, profileid string, getTime time.Duration) {} + +func (st *StatsTCP) RecordDBQueryFailure(queryType, publisher, profile string) {} + +// getStatsKeyIndexForResponseTime returns respective stats key for a given responsetime +func getStatsKeyIndexForResponseTime(responseTime int) int { + statKey := 0 + switch { + case responseTime >= 2000: + statKey = statsKeyA2000 + case responseTime >= 1500: + statKey = statsKeyA1500 + case responseTime >= 1000: + statKey = statsKeyA1000 + case responseTime >= 900: + statKey = statsKeyA900 + case responseTime >= 800: + statKey = statsKeyA800 + case responseTime >= 700: + statKey = statsKeyA700 + case responseTime >= 600: + statKey = statsKeyA600 + case responseTime >= 500: + statKey = statsKeyA500 + case responseTime >= 400: + statKey = statsKeyA400 + case responseTime >= 300: + statKey = statsKeyA300 + case responseTime >= 200: + statKey = statsKeyA200 + case responseTime >= 100: + statKey = statsKeyA100 + case responseTime >= 50: + statKey = statsKeyA50 + default: // below 50 ms + statKey = statsKeyL50 + } + return statKey +} + +func (st *StatsTCP) Shutdown() { + st.statsClient.ShutdownProcess() +} + +func (st *StatsTCP) RecordRequest(labels metrics.Labels) {} +func (st *StatsTCP) RecordLurlSent(labels metrics.LurlStatusLabels) {} +func (st *StatsTCP) RecordLurlBatchSent(labels metrics.LurlBatchStatusLabels) {} +func (st *StatsTCP) RecordBids(pubid, profileid, biddder, deal string) {} +func (st *StatsTCP) RecordPartnerTimeoutRequests(pubid, profileid, bidder string) {} +func (st *StatsTCP) RecordCtvUaAccuracy(pubId, status string) {} +func (st *StatsTCP) RecordSendLoggerDataTime(requestType, profileid string, sendTime time.Duration) {} +func (st *StatsTCP) RecordRequestTime(requestType string, requestTime time.Duration) {} +func (st *StatsTCP) RecordOWServerPanic(endpoint, methodName, nodeName, podName string) {} diff --git a/modules/pubmatic/openwrap/metrics/stats/tcp_stats_test.go b/modules/pubmatic/openwrap/metrics/stats/tcp_stats_test.go new file mode 100644 index 00000000000..70f6a3c1a36 --- /dev/null +++ b/modules/pubmatic/openwrap/metrics/stats/tcp_stats_test.go @@ -0,0 +1,1120 @@ +package stats + +import ( + "fmt" + "net" + "net/http" + "testing" + "time" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/stretchr/testify/assert" +) + +func TestInitTCPStatsClient(t *testing.T) { + + type args struct { + endpoint string + pubInterval, pubThreshold, retries, dialTimeout, + keepAliveDur, maxIdleConn, maxIdleConnPerHost, respHeaderTimeout, + maxChannelLength, poolMaxWorkers, poolMaxCapacity int + } + + type want struct { + client *StatsTCP + err error + } + + tests := []struct { + name string + args args + want want + }{ + { + name: "returns_error", + args: args{ + endpoint: "", + pubInterval: 10, + pubThreshold: 10, + retries: 3, + dialTimeout: 10, + keepAliveDur: 10, + maxIdleConn: 10, + maxIdleConnPerHost: 10, + }, + want: want{ + client: nil, + err: fmt.Errorf("invalid stats client configurations:stat server endpoint cannot be empty"), + }, + }, + { + name: "returns_valid_client", + args: args{ + endpoint: "http://10.10.10.10:8000/stat", + pubInterval: 10, + pubThreshold: 10, + retries: 3, + dialTimeout: 10, + keepAliveDur: 10, + maxIdleConn: 10, + maxIdleConnPerHost: 10, + }, + want: want{ + client: &StatsTCP{ + statsClient: &Client{ + endpoint: "http://10.10.10.10:8000/stat", + httpClient: &http.Client{ + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 10 * time.Second, + KeepAlive: 10 * time.Minute, + }).DialContext, + MaxIdleConns: 10, + MaxIdleConnsPerHost: 10, + ResponseHeaderTimeout: 30 * time.Second, + }, + }, + config: &config{ + Endpoint: "http://10.10.10.10:8000/stat", + PublishingInterval: 5, + PublishingThreshold: 1000, + Retries: 3, + DialTimeout: 10, + KeepAliveDuration: 15, + MaxIdleConns: 10, + MaxIdleConnsPerHost: 10, + retryInterval: 100, + MaxChannelLength: 1000, + ResponseHeaderTimeout: 30, + PoolMaxWorkers: minPoolWorker, + PoolMaxCapacity: minPoolCapacity, + }, + pubChan: make(chan stat, 1000), + statMap: map[string]int{}, + }, + }, + err: nil, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, err := initTCPStatsClient(tt.args.endpoint, + tt.args.pubInterval, tt.args.pubThreshold, tt.args.retries, tt.args.dialTimeout, tt.args.keepAliveDur, + tt.args.maxIdleConn, tt.args.maxIdleConnPerHost, tt.args.respHeaderTimeout, tt.args.maxChannelLength, + tt.args.poolMaxWorkers, tt.args.poolMaxCapacity) + + assert.Equal(t, tt.want.err, err) + if err == nil { + compareClient(tt.want.client.statsClient, client.statsClient, t) + } + }) + } +} + +func TestRecordFunctions(t *testing.T) { + + initStatKeys("N:P", "N:P") + + type args struct { + statTCP *StatsTCP + } + + type want struct { + expectedkeyVal map[string]int + channelSize int + } + + tests := []struct { + name string + args args + want want + callRecord func(*StatsTCP) + }{ + { + name: "RecordOpenWrapServerPanicStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + statKeys[statsKeyOpenWrapServerPanic]: 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordOpenWrapServerPanicStats("", "") + }, + }, + { + name: "RecordPublisherPartnerNoCookieStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyPublisherPartnerNoCookieRequests], "5890", "pubmatic"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPublisherPartnerNoCookieStats("5890", "pubmatic") + }, + }, + { + name: "RecordPartnerTimeoutErrorStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyPartnerTimeoutErrorRequests], "5890", "pubmatic"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPartnerResponseErrors("5890", "pubmatic", models.PartnerErrTimeout) + }, + }, + { + name: "RecordNobidErrorStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyNobidErrorRequests], "5890", "pubmatic"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPartnerResponseErrors("5890", "pubmatic", models.PartnerErrNoBid) + }, + }, + { + name: "RecordUnkownPrebidErrorStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyUnknownPrebidErrorResponse], "5890", "pubmatic"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPartnerResponseErrors("5890", "pubmatic", models.PartnerErrUnknownPrebidError) + }, + }, + { + name: "RecordSlotNotMappedErrorStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeySlotunMappedErrorRequests], "5890", "pubmatic"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPartnerConfigErrors("5890", "1234", "pubmatic", models.PartnerErrSlotNotMapped) + }, + }, + { + name: "RecordMisConfigurationErrorStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyMisConfErrorRequests], "5890", "pubmatic"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPartnerConfigErrors("5890", "1234", "pubmatic", models.PartnerErrMisConfig) + }, + }, + { + name: "RecordPublisherProfileRequests", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyPublisherProfileRequests], "5890", "pubmatic"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPublisherProfileRequests("5890", "pubmatic") + }, + }, + { + name: "RecordPublisherInvalidProfileRequests", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 3), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyPublisherInvProfileVideoRequests], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyPublisherInvProfileAMPRequests], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyPublisherInvProfileRequests], "5890", "pubmatic"): 1, + }, + channelSize: 3, + }, + callRecord: func(st *StatsTCP) { + st.RecordPublisherInvalidProfileRequests("video", "5890", "pubmatic") + st.RecordPublisherInvalidProfileRequests("amp", "5890", "pubmatic") + st.RecordPublisherInvalidProfileRequests("", "5890", "pubmatic") + }, + }, + { + name: "RecordPublisherInvalidProfileImpressions", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyPublisherInvProfileImpressions], "5890", "pubmatic"): 10, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPublisherInvalidProfileImpressions("5890", "pubmatic", 10) + }, + }, + { + name: "RecordNobidErrPrebidServerRequests", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyNobidErrPrebidServerRequests], "5890"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordNobidErrPrebidServerRequests("5890", 0) + }, + }, + { + name: "RecordNobidErrPrebidServerResponse", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyNobidErrPrebidServerResponse], "5890"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordNobidErrPrebidServerResponse("5890") + }, + }, + { + name: "RecordInvalidCreativeStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyInvalidCreatives], "5890", "pubmatic"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordInvalidCreativeStats("5890", "pubmatic") + }, + }, + { + name: "RecordPlatformPublisherPartnerReqStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyPlatformPublisherPartnerRequests], "web", "5890", "pubmatic"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPlatformPublisherPartnerReqStats("web", "5890", "pubmatic") + }, + }, + { + name: "RecordPlatformPublisherPartnerResponseStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyPlatformPublisherPartnerResponses], "web", "5890", "pubmatic"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPlatformPublisherPartnerResponseStats("web", "5890", "pubmatic") + }, + }, + { + name: "RecordPublisherResponseEncodingErrorStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyPublisherResponseEncodingErrors], "5890"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPublisherResponseEncodingErrorStats("5890") + }, + }, + { + name: "RecordPartnerResponseTimeStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 20), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyL50], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA50], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA100], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA200], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA300], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA400], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA500], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA600], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA700], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA800], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA900], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA1000], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA1500], "5890", "pubmatic"): 1, + fmt.Sprintf(statKeys[statsKeyA2000], "5890", "pubmatic"): 1, + }, + channelSize: 14, + }, + callRecord: func(st *StatsTCP) { + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 10) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 60) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 110) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 210) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 310) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 410) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 510) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 610) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 710) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 810) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 910) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 1010) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 1510) + st.RecordPartnerResponseTimeStats("5890", "pubmatic", 2010) + }, + }, + { + name: "RecordPublisherResponseTimeStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 20), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyL50], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA50], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA100], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA200], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA300], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA400], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA500], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA600], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA700], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA800], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA900], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA1000], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA1500], "5890", "overall"): 1, + fmt.Sprintf(statKeys[statsKeyA2000], "5890", "overall"): 1, + }, + channelSize: 14, + }, + callRecord: func(st *StatsTCP) { + st.RecordPublisherResponseTimeStats("5890", 10) + st.RecordPublisherResponseTimeStats("5890", 60) + st.RecordPublisherResponseTimeStats("5890", 110) + st.RecordPublisherResponseTimeStats("5890", 210) + st.RecordPublisherResponseTimeStats("5890", 310) + st.RecordPublisherResponseTimeStats("5890", 410) + st.RecordPublisherResponseTimeStats("5890", 510) + st.RecordPublisherResponseTimeStats("5890", 610) + st.RecordPublisherResponseTimeStats("5890", 710) + st.RecordPublisherResponseTimeStats("5890", 810) + st.RecordPublisherResponseTimeStats("5890", 910) + st.RecordPublisherResponseTimeStats("5890", 1010) + st.RecordPublisherResponseTimeStats("5890", 1510) + st.RecordPublisherResponseTimeStats("5890", 2010) + }, + }, + { + name: "RecordPublisherWrapperLoggerFailure", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyLoggerErrorRequests], "5890", "1234", "0"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPublisherWrapperLoggerFailure("5890", "1234", "0") + }, + }, + { + name: "RecordPrebidTimeoutRequests", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyPrebidTORequests], "5890", "1234"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPrebidTimeoutRequests("5890", "1234") + }, + }, + { + name: "RecordSSTimeoutRequests", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeySsTORequests], "5890", "1234"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordSSTimeoutRequests("5890", "1234") + }, + }, + { + name: "RecordUidsCookieNotPresentErrorStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyNoUIDSErrorRequest], "5890", "1234"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordUidsCookieNotPresentErrorStats("5890", "1234") + }, + }, + { + name: "RecordVideoInstlImpsStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyVideoInterstitialImpressions], "5890", "1234"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordVideoInstlImpsStats("5890", "1234") + }, + }, + { + name: "RecordImpDisabledViaConfigStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 2), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyVideoImpDisabledViaConfig], "5890", "1234"): 1, + fmt.Sprintf(statKeys[statsKeyBannerImpDisabledViaConfig], "5890", "1234"): 1, + }, + channelSize: 2, + }, + callRecord: func(st *StatsTCP) { + st.RecordImpDisabledViaConfigStats("video", "5890", "1234") + st.RecordImpDisabledViaConfigStats("banner", "5890", "1234") + }, + }, + { + name: "RecordVideoImpDisabledViaConnTypeStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 2), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyVideoImpDisabledViaConnType], "5890", "1234"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordVideoImpDisabledViaConnTypeStats("5890", "1234") + }, + }, + { + name: "RecordPreProcessingTimeStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 5), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyPrTimeAbv100], "5890"): 1, + fmt.Sprintf(statKeys[statsKeyPrTimeAbv50], "5890"): 1, + fmt.Sprintf(statKeys[statsKeyPrTimeAbv10], "5890"): 1, + fmt.Sprintf(statKeys[statsKeyPrTimeAbv1], "5890"): 1, + fmt.Sprintf(statKeys[statsKeyPrTimeBlw1], "5890"): 1, + }, + channelSize: 5, + }, + callRecord: func(st *StatsTCP) { + st.RecordPreProcessingTimeStats("5890", 0) + st.RecordPreProcessingTimeStats("5890", 5) + st.RecordPreProcessingTimeStats("5890", 15) + st.RecordPreProcessingTimeStats("5890", 75) + st.RecordPreProcessingTimeStats("5890", 105) + }, + }, + { + name: "RecordStatsKeyCTVPrebidFailedImpression", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 5), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyCTVPrebidFailedImpression], 1, "5890", "1234"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordStatsKeyCTVPrebidFailedImpression(1, "5890", "1234") + }, + }, + { + name: "RecordCTVRequests", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 5), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyCTVRequests], "5890", "web"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordCTVRequests("5890", "web") + }, + }, + { + name: "RecordBadRequests", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 7), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyAMPBadRequests]): 1, + fmt.Sprintf(statKeys[statsKeyVideoBadRequests]): 1, + fmt.Sprintf(statKeys[statsKey25BadRequests]): 1, + fmt.Sprintf(statKeys[statsKeyCTVBadRequests], "json", 100): 1, + fmt.Sprintf(statKeys[statsKeyCTVBadRequests], "openwrap", 200): 1, + fmt.Sprintf(statKeys[statsKeyCTVBadRequests], "ortb", 300): 1, + fmt.Sprintf(statKeys[statsKeyCTVBadRequests], "vast", 400): 1, + }, + channelSize: 7, + }, + callRecord: func(st *StatsTCP) { + st.RecordBadRequests("amp", 1) + st.RecordBadRequests("video", 1) + st.RecordBadRequests("v25", 1) + st.RecordBadRequests("json", 100) + st.RecordBadRequests("openwrap", 200) + st.RecordBadRequests("ortb", 300) + st.RecordBadRequests("vast", 400) + }, + }, + { + name: "RecordCTVHTTPMethodRequests", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyCTVHTTPMethodRequests], "ortb", "5890", "GET"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordCTVHTTPMethodRequests("ortb", "5890", "GET") + }, + }, + { + name: "RecordCTVInvalidReasonCount", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 5), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyCTVValidationErr], 100, "5890"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordCTVInvalidReasonCount(100, "5890") + }, + }, + { + name: "RecordCTVReqImpsWithDbConfigCount", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 5), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyCTVReqImpstWithConfig], "db", "5890"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordCTVReqImpsWithDbConfigCount("5890") + }, + }, + { + name: "RecordCTVReqImpsWithReqConfigCount", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 5), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyCTVReqImpstWithConfig], "req", "5890"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordCTVReqImpsWithReqConfigCount("5890") + }, + }, + { + name: "RecordAdPodGeneratedImpressionsCount", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 4), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyTotalAdPodImpression], "1-3", "5890"): 1, + fmt.Sprintf(statKeys[statsKeyTotalAdPodImpression], "4-6", "5890"): 1, + fmt.Sprintf(statKeys[statsKeyTotalAdPodImpression], "7-9", "5890"): 1, + fmt.Sprintf(statKeys[statsKeyTotalAdPodImpression], "9+", "5890"): 1, + }, + channelSize: 4, + }, + callRecord: func(st *StatsTCP) { + st.RecordAdPodGeneratedImpressionsCount(3, "5890") + st.RecordAdPodGeneratedImpressionsCount(6, "5890") + st.RecordAdPodGeneratedImpressionsCount(9, "5890") + st.RecordAdPodGeneratedImpressionsCount(11, "5890") + }, + }, + { + name: "RecordRequestAdPodGeneratedImpressionsCount", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyReqTotalAdPodImpression], "5890"): 2, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordRequestAdPodGeneratedImpressionsCount(2, "5890") + }, + }, + { + name: "RecordReqImpsWithAppContentCount", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyContentObjectPresent], "app", "5890"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordReqImpsWithContentCount("5890", models.ContentTypeApp) + }, + }, + { + name: "RecordReqImpsWithSiteContentCount", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyContentObjectPresent], "site", "5890"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordReqImpsWithContentCount("5890", models.ContentTypeSite) + }, + }, + { + name: "RecordAdPodImpressionYield", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyReqImpDurationYield], 10, 1, "5890"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordAdPodImpressionYield(10, 1, "5890") + }, + }, + { + name: "RecordCTVReqCountWithAdPod", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyReqWithAdPodCount], "5890", "1234"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordCTVReqCountWithAdPod("5890", "1234") + }, + }, + { + name: "RecordPBSAuctionRequestsStats", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyPBSAuctionRequests]): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPBSAuctionRequestsStats() + }, + }, + { + name: "RecordInjectTrackerErrorCount", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyInjectTrackerErrorCount], "banner", "5890", "1234"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordInjectTrackerErrorCount("banner", "5890", "1234") + }, + }, + { + name: "RecordBidResponseByDealCountInPBS", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsBidResponsesByDealUsingPBS], "5890", "1234", "pubmatic", "pubdeal"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordBidResponseByDealCountInPBS("5890", "1234", "pubmatic", "pubdeal") + }, + }, + { + name: "RecordBidResponseByDealCountInHB", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsBidResponsesByDealUsingHB], "5890", "1234", "pubmatic", "pubdeal"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordBidResponseByDealCountInHB("5890", "1234", "pubmatic", "pubdeal") + }, + }, + { + name: "RecordPartnerTimeoutInPBS", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 1), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsPartnerTimeoutInPBS], "5890", "1234", "pubmatic"): 1, + }, + channelSize: 1, + }, + callRecord: func(st *StatsTCP) { + st.RecordPartnerTimeoutInPBS("5890", "1234", "pubmatic") + }, + }, + { + name: "RecordPublisherRequests", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 6), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyAMPPublisherRequests], "5890"): 1, + fmt.Sprintf(statKeys[statsKeyVideoPublisherRequests], "5890"): 1, + fmt.Sprintf(statKeys[statsKey25PublisherRequests], "app", "5890"): 1, + fmt.Sprintf(statKeys[statsKeyCTVPublisherRequests], "ortb", "app", "5890"): 1, + fmt.Sprintf(statKeys[statsKeyCTVPublisherRequests], "json", "app", "5890"): 1, + fmt.Sprintf(statKeys[statsKeyCTVPublisherRequests], "vast", "display", "5890"): 1, + }, + channelSize: 6, + }, + callRecord: func(st *StatsTCP) { + st.RecordPublisherRequests("amp", "5890", "") + st.RecordPublisherRequests("video", "5890", "") + st.RecordPublisherRequests("v25", "5890", "in-app") + st.RecordPublisherRequests("ortb", "5890", "in-app") + st.RecordPublisherRequests("json", "5890", "app") + st.RecordPublisherRequests("vast", "5890", "display") + }, + }, + { + name: "RecordCacheErrorRequests", + args: args{ + statTCP: &StatsTCP{ + &Client{ + pubChan: make(chan stat, 2), + }, + }, + }, + want: want{ + expectedkeyVal: map[string]int{ + fmt.Sprintf(statKeys[statsKeyAMPCacheError], "5890", "1234"): 1, + fmt.Sprintf(statKeys[statsKeyVideoCacheError], "5890", "1234"): 1, + }, + channelSize: 2, + }, + callRecord: func(st *StatsTCP) { + st.RecordCacheErrorRequests("amp", "5890", "1234") + st.RecordCacheErrorRequests("video", "5890", "1234") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + tt.callRecord(tt.args.statTCP) + + close(tt.args.statTCP.statsClient.pubChan) + assert.Equal(t, tt.want.channelSize, len(tt.args.statTCP.statsClient.pubChan)) + for stat := range tt.args.statTCP.statsClient.pubChan { + assert.Equalf(t, tt.want.expectedkeyVal[stat.Key], stat.Value, + "Mismatched value for key [%s]", stat.Key) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/models/adcom.go b/modules/pubmatic/openwrap/models/adcom.go new file mode 100644 index 00000000000..22326cc904d --- /dev/null +++ b/modules/pubmatic/openwrap/models/adcom.go @@ -0,0 +1,130 @@ +package models + +import "github.com/prebid/openrtb/v19/adcom1" + +func GetAPIFramework(api []int) []adcom1.APIFramework { + if api == nil { + return nil + } + adComAPIs := make([]adcom1.APIFramework, len(api)) + + for index, value := range api { + adComAPIs[index] = adcom1.APIFramework(value) + } + + return adComAPIs +} + +func GetPlaybackMethod(playbackMethods []int) []adcom1.PlaybackMethod { + if playbackMethods == nil { + return nil + } + methods := make([]adcom1.PlaybackMethod, len(playbackMethods)) + + for index, value := range playbackMethods { + methods[index] = adcom1.PlaybackMethod(value) + } + + return methods +} + +func GetDeliveryMethod(deliveryMethods []int) []adcom1.DeliveryMethod { + if deliveryMethods == nil { + return nil + } + methods := make([]adcom1.DeliveryMethod, len(deliveryMethods)) + + for index, value := range deliveryMethods { + methods[index] = adcom1.DeliveryMethod(value) + } + + return methods +} + +func GetCompanionType(companionTypes []int) []adcom1.CompanionType { + if companionTypes == nil { + return nil + } + adcomCompanionTypes := make([]adcom1.CompanionType, len(companionTypes)) + + for index, value := range companionTypes { + adcomCompanionTypes[index] = adcom1.CompanionType(value) + } + + return adcomCompanionTypes +} + +func GetCreativeAttributes(creativeAttributes []int) []adcom1.CreativeAttribute { + if creativeAttributes == nil { + return nil + } + adcomCreatives := make([]adcom1.CreativeAttribute, len(creativeAttributes)) + + for index, value := range creativeAttributes { + adcomCreatives[index] = adcom1.CreativeAttribute(value) + } + + return adcomCreatives +} + +func GetProtocol(protocols []int) []adcom1.MediaCreativeSubtype { + if protocols == nil { + return nil + } + adComProtocols := make([]adcom1.MediaCreativeSubtype, len(protocols)) + + for index, value := range protocols { + adComProtocols[index] = adcom1.MediaCreativeSubtype(value) + } + + return adComProtocols +} + +// BannerAdType +// Types of ads that can be accepted by the exchange unless restricted by publisher site settings. +type BannerAdType int8 + +const ( + BannerAdTypeXHTMLTextAd BannerAdType = 1 // XHTML Text Ad (usually mobile) + BannerAdTypeXHTMLBannerAd BannerAdType = 2 // XHTML Banner Ad. (usually mobile) + BannerAdTypeJavaScriptAd BannerAdType = 3 // JavaScript Ad; must be valid XHTML (i.e., Script Tags Included) + BannerAdTypeIframe BannerAdType = 4 // iframe +) + +func GetBannderAdType(adTypes []int) []BannerAdType { + if adTypes == nil { + return nil + } + bannerAdTypes := make([]BannerAdType, len(adTypes)) + + for index, value := range adTypes { + bannerAdTypes[index] = BannerAdType(value) + } + + return bannerAdTypes +} + +func GetExpandableDirection(expdirs []int) []adcom1.ExpandableDirection { + if expdirs == nil { + return nil + } + adComExDir := make([]adcom1.ExpandableDirection, len(expdirs)) + + for index, value := range expdirs { + adComExDir[index] = adcom1.ExpandableDirection(value) + } + + return adComExDir +} + +func GetConnectionType(connectionType []int) []adcom1.ConnectionType { + if connectionType == nil { + return nil + } + adComExDir := make([]adcom1.ConnectionType, len(connectionType)) + for index, value := range connectionType { + adComExDir[index] = adcom1.ConnectionType(value) + } + + return adComExDir +} diff --git a/modules/pubmatic/openwrap/models/adunitconfig/adunitconfig.go b/modules/pubmatic/openwrap/models/adunitconfig/adunitconfig.go new file mode 100644 index 00000000000..7a29f7e896c --- /dev/null +++ b/modules/pubmatic/openwrap/models/adunitconfig/adunitconfig.go @@ -0,0 +1,84 @@ +package adunitconfig + +import ( + "encoding/json" + "errors" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +var ErrAdUnitUnmarshal = errors.New("unmarshal error adunitconfig") + +// AdUnitConfig type definition for Ad Unit config parsed from stored config JSON +type AdUnitConfig struct { + ConfigPattern string `json:"configPattern,omitempty"` + Regex bool `json:"regex,omitempty"` + Config map[string]*AdConfig `json:"config"` + // TODO add seperate default field + // Default map[string]*AdConfig `json:"default"` +} +type Content struct { + Mappings map[string]openrtb_ext.TransparencyRule `json:"mappings,omitempty"` + Dimension []string `json:"dimension,omitempty"` +} +type Transparency struct { + Content Content `json:"content,omitempty"` +} + +type BannerConfig struct { + openrtb2.Banner + ClientConfig json.RawMessage `json:"clientconfig,omitempty"` +} + +type Banner struct { + Enabled *bool `json:"enabled,omitempty"` + Config *BannerConfig `json:"config,omitempty"` +} + +type VideoConfig struct { + openrtb2.Video + ConnectionType []int `json:"connectiontype,omitempty"` + ClientConfig json.RawMessage `json:"clientconfig,omitempty"` +} + +type Native struct { + Enabled *bool `json:"enabled,omitempty"` + Config *NativeConfig `json:"config,omitempty"` +} + +type NativeConfig struct { + openrtb2.Native + ClientConfig json.RawMessage `json:"clientconfig,omitempty"` +} + +type Video struct { + Enabled *bool `json:"enabled,omitempty"` + Config *VideoConfig `json:"config,omitempty"` +} + +// Struct for UniversalPixel +type UniversalPixel struct { + Id int `json:"id,omitempty"` + Pixel string `json:"pixel,omitempty"` + PixelType string `json:"pixeltype,omitempty"` + Pos string `json:"pos,omitempty"` + MediaType string `json:"mediatype,omitempty"` + Partners []string `json:"partners,omitempty"` +} + +type AdConfig struct { + BidFloor *float64 `json:"bidfloor,omitempty"` + 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"` +} diff --git a/modules/pubmatic/openwrap/models/amp.go b/modules/pubmatic/openwrap/models/amp.go new file mode 100644 index 00000000000..1cd1a513b93 --- /dev/null +++ b/modules/pubmatic/openwrap/models/amp.go @@ -0,0 +1,19 @@ +package models + +import ( + "net/http" + "strconv" +) + +func GetQueryParamRequestExtWrapper(request *http.Request) (RequestExtWrapper, int, error) { + extWrapper := RequestExtWrapper{ + SSAuctionFlag: -1, + } + + values := request.URL.Query() + 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 +} diff --git a/modules/pubmatic/openwrap/models/bidders.go b/modules/pubmatic/openwrap/models/bidders.go new file mode 100644 index 00000000000..ccb3833e8bb --- /dev/null +++ b/modules/pubmatic/openwrap/models/bidders.go @@ -0,0 +1,24 @@ +package models + +// Bidder Names +const ( + BidderVASTBidder = "vastbidder" +) + +// S2S Hard Coded Alias Bidder Name +const ( + BidderAdGenerationAlias = "adg" + BidderAndBeyondAlias = "andbeyond" + BidderDistrictmAlias = "districtm" + BidderDistrictmDMXAlias = "districtmDMX" + BidderPubMaticSecondaryAlias = "pubmatic2" + BidderMediaFuseAlias = "mediafuse" +) + +const ( + SS_PM_ADSLOT = "adSlot" + SS_PM_WRAPPER = "wrapper" + SS_PM_PUBID = "publisherId" + SS_PM_PROFILE_ID = "profile" + SS_PM_VERSION_ID = "version" +) diff --git a/modules/pubmatic/openwrap/models/constants.go b/modules/pubmatic/openwrap/models/constants.go new file mode 100755 index 00000000000..e8c38e85835 --- /dev/null +++ b/modules/pubmatic/openwrap/models/constants.go @@ -0,0 +1,492 @@ +package models + +const ( + DEFAULT_PUB_ID = 34576 // Default PubID to get generic data like regex for browsers etc + PARTNER_ID = "partnerId" + ADAPTER_ID = "adapterId" + PARTNER_ACCOUNT_NAME = "partnerName" + ADAPTER_NAME = "adapterName" + PREBID_PARTNER_NAME = "prebidPartnerName" + BidderCode = "bidderCode" + IsAlias = "isAlias" + PROTOCOL = "protocol" + SERVER_SIDE_FLAG = "serverSideEnabled" + DisplayVersionID = "displayVersionId" + KEY_PUBLISHER_ID = "publisherId" + KEY_PROFILE_ID = "profileId" + KEY_SLOT_NAME = "slotName" + LEVEL = "level" + KEY_GEN_PATTERN = "kgp" + TIMEOUT = "timeout" + AdserverKey = "adserver" + MopubAdserver = "MoPub" + CustomAdserver = "CUSTOM" + PriceGranularityKey = "priceGranularity" + VideoAdDurationKey = "videoAdDuration" + VideoAdDurationMatchingKey = "videoAdDurationMatching" + REVSHARE = "rev_share" + THROTTLE = "throttle" + REFRESH_INTERVAL = "refreshInterval" + CreativeType = "crtype" + GDPR_ENABLED = "gdpr" + PLATFORM_KEY = "platform" + SendAllBidsKey = "sendAllBids" + SSTimeoutKey = "ssTimeout" + PWC = "awc" + MAX_SLOT_COUNT = 5000 + SITE_CACHE_KEY = "site" + TAG_CACHE_KEY = "adtag" + GA_ID_CACHE_KEY = "gaid" + FLOOR_CACHE_KEY = "floor" + PUBMATIC = "PubMatic" + PUBMATIC_TIMEOUT = "PubmaticTimeout" + PUBMATIC_PROTOCOL = "/gads" + PUBMATIC_LEVEL = "multi" + PUBMATIC_SS_FLAG = "1" + PUBMATIC_PARTNER_ID_STRING = "1" + PUBMATIC_ADAPTER_ID_STRING = "1" + VersionLevelConfigID = -1 + ERROR_CODE = "ErrorCode" + ERROR_STRING = "Error" + PUBMATIC_PARTNER_ID = 1 + PUBMATIC_ADAPTER_ID = 1 + DEFAULT_STRING = "" + DEFAULT_INT = 0 + DEFAULT_FLOAT = 0.00 + BID_PRECISION = 2 + Debug = "debug" + WrapperLoggerDebug = "owLoggerDebug" + KEY_OW_SLOT_NAME = "owSlotName" + VENDORID = "vendorId" + BidderPubMatic = "pubmatic" + //ADSERVER_URL used by S2S to redirect the OW bids if owredirect parameter is not found in video/json + ADSERVER_URL = "adServerUrl" + + AdServerCurrency = "adServerCurrency" + + MarketplaceBidders = "marketplaceBidders" + + UserAgent = "UserAgent" + IP = "ip" + StoreURL = "StoreURL" + Consent = "consent" + GDPR = "gdpr" + PublisherID = "pubid" + ProfileID = "profileID" + VersionID = "versionID" + Origin = "origin" + + DEFAULT_DEALCHANNEL = "PMP" + + WLPUBID = "pubid" + WLJSON = "json" + WLGDPR = "gdEn" + USER_AGENT_HEADER = "User-Agent" + IP_HEADER = "SOURCE_IP" + + GADS_UNMAPPED_SLOT_ERROR_MSG = "Slot not mapped" + GADS_MISSING_CONF_ERROR_MSG = "Missing Configuration" + TIMEOUT_ERROR_MSG = "Timeout Error" + NO_BID_PREBID_MSG = "No Bid" + PARTNER_TIMEOUT_ERR_MSG = "Partner Timed out" + PREBID_DEFAULT_TIMEOUT_ERR_MSG = "Timed out" + INVALID_CONFIGURATION_ERR_MSG = "Invalid Configuration" + NO_GDPR_CONSENT_ERR_MSG = "No Consent Present" + API_RESPONSE_ERROR_MSG = "API Error" + INVALID_IMPRESSION_ERR_MSG = "No Valid Impression Found" + CACHE_PUT_FAILED_ERROR_MSG = "Cache PUT Failed" + INVALID_PARAMETERS_ERROR_MSG = "Invalid Parameters" + BANNER_VIDEO_DISABLED_ERROR_MSG = "Banner/Video disabled through config" + // PrebidUnknownErrorMsg is the Error message for Unknown Error returned from prebid-server + PrebidUnknownErrorMsg = "Unknown error received from Prebid" + + ALL_PARTNERS_THROTTLED_ERROR_MSG = "All partners throttled" + PARTNER_THROTTLED_ERROR_MSG = "Partner throttled" + PriceGranularityCustom = "custom" // contains `custom` price granularity as value + PriceGranularityCustomConfig = "customPriceGranularityConfig" // key which holds configurations around custom price granularity + + // Below is added for Comapring error returned by Prebid Server + PARTNER_CONTEXT_DEADLINE = "context deadline exceeded" + INVALID_CREATIVE_ERROR_MSG = "Invalid Creative" + + //constants for macros of logger/tracker keys + MacroPartnerName = "${PARTNER_NAME}" + MacroBidderCode = "${BIDDER_CODE}" + MacroKGPV = "${KGPV}" + MacroGrossECPM = "${G_ECPM}" + MacroNetECPM = "${N_ECPM}" + MacroBidID = "${BID_ID}" + MacroOrigBidID = "${ORIGBID_ID}" + MacroSlotID = "${SLOT_ID}" + MacroAdunit = "${ADUNIT}" + MacroRewarded = "${REWARDED}" + + //constants for targetting keys in AMP + PWT_PUBID = "pwtpubid" + PWT_PROFILEID = "pwtprofid" + PWT_VERSIONID = "pwtverid" + PWT_ECPM = "pwtecp" + PWT_BIDSTATUS = "pwtbst" + PWT_DEALID = "pwtdid" + PWT_SLOTID = "pwtsid" + PWT_PARTNERID = "pwtpid" + PWT_CACHEID = "pwtcid" + PWT_CACHEURL = "pwtcurl" + PWT_CACHE_PATH = "pwtcpath" + PWT_PLATFORM = "pwtplt" + PWT_SZ = "pwtsz" + PWT_DURATION = "pwtdur" + PwtBidID = "pwtbidid" // Represents bid.id value from oRTB response + PwtPb = "pwtpb" + PwtCat = "pwtcat" + PwtPbCatDur = "pwtpb_cat_dur" + + //constants for query params in AMP request + PUBID_KEY = "pubId" + PROFILEID_KEY = "profId" + ADUNIT_KEY = "auId" + MULTISIZE_KEY = "ms" + PAGEURL_KEY = "purl" + WIDTH_KEY = "w" + HEIGHT_KEY = "h" + VERSION_KEY = "pwtv" + DEBUG_KEY = "pwtvc" + ResponseFormatKey = "f" + ConsentStringKey = "consent_string" + GDPRAppliesKey = "gdpr_applies" + ConsentTypeKey = "consent_type" + CanonicalUrl = "curl" + TargetingKey = "targeting" + + AMP_CACHE_PATH = "/cache" + AMP_ORIGIN = "__amp_source_origin" + ResponseFormatJSON = "json" + ResponseFormatRedirect = "redirect" + Test = "test" + PubmaticTest = "pubmaticTest" + + // constants for query params in Video request + OWRedirectURLKey = "owredirect" + CustParams = "cust_params" + MimeTypes = "pwtmime" + InventoryUnitKey = "iu" + InventoryUnitMacroKey = "pwtm_iu" + Correlator = "correlator" + MacroPrefix = "pwtm_" + GDPRFlag = "pwtgdpr" + CCPAUSPrivacyKey = "pwtccpa" + ConsentString = "pwtcnst" + AppId = "pwtappid" + AppRequest = "pwtapp" + DeviceLMT = "pwtlmt" + DeviceDNT = "pwtdnt" + UserID = "pwtuid" + ContentTransparency = "owcontenttransparency" + FloorValue = "floor_val" + FloorCurrency = "floor_cur" + + // constants for error related query params to be added to DFP call + ErrorKey = "pwterr" + ErrorMsg = "pwterrmsg" + PartnerConfigNotFoundErr = "1" + CachePutFailedErr = "2" + TimeoutErr = "3" + ParameterValidationErr = "4" + SlotNotMappedErr = "5" + + //constants for video + VIDEO_CACHE_PATH = "/cache" + VideoSizeSuffix = "v" + PartnerURLPlaceholder = "$PARTNER_URL_PLACEHOLDER" + TrackerPlaceholder = "$TRACKER_PLACEHOLDER" + ErrorPlaceholder = "$ERROR_PLACEHOLDER" + ImpressionElement = "Impression" + ErrorElement = "Error" + VASTAdElement = ".//VAST/Ad" + AdWrapperElement = "./Wrapper" + AdInlineElement = "./InLine" + VASTAdWrapperElement = ".//VAST/Ad/Wrapper" + VASTAdInlineElement = ".//VAST/Ad/InLine" + CdataPrefix = "" + HTTPProtocol = "http" + HTTPSProtocol = "https" + VASTImpressionURLTemplate = `` + VASTErrorURLTemplate = `` + VastWrapper = `PubMatic Wrapper` + VASTImpressionURLTemplate + VASTErrorURLTemplate + `` + + //constants for wrapper platforms + PLATFORM_DISPLAY = "display" + PLATFORM_AMP = "amp" + PLATFORM_APP = "in-app" + PLATFORM_VIDEO = "video" + PlatformAppTargetingKey = "inapp" + HB_PLATFORM_APP = "app" + + //constants for headers + ORIGIN = "origin" + KADUSERCOOKIE = "KADUSERCOOKIE" + COOKIE = "Cookie" + WrapperLoggerImpID = "wiid" + UidCookieName = "uids" + + //constant for gzip response + AcceptEncodingHeader = "Accept-Encoding" + GZIPEncoding = "gzip" + + //bidresponse extension + ResponseTime = "responsetimemillis" + ResponseExtAdPod = "adpod" + MatchedImpression = "matchedimpression" + LogInfoKey = "loginfo" + LogInfoLoggerKey = "logger" + LogInfoTrackerKey = "tracker" + SendAllBidsFlagKey = "sendallbids" + LoggerKey = "owlogger" + + //keys for reading values from Impression Extension JSON + SKAdnetwork = "skadn" + PrebidKey = "prebid" + ImpExtData = "data" + + //Node and Pod names for K8S + DEFAULT_NODENAME = "Default_Node" + DEFAULT_PODNAME = "Default_Pod" + ENV_VAR_NODE_NAME = "MY_NODE_NAME" + ENV_VAR_POD_NAME = "MY_POD_NAME" + + // PrebidTargetingKeyPrefix is Prebid's prefix for ext.Prebid.targeting keys + PrebidTargetingKeyPrefix = "hb_" + // OWTargetingKeyPrefix is OpenWrap's prefix for ext.Prebid.targeting keys + OWTargetingKeyPrefix = "pwt" + + //constants for reading adunit Config JSON + AdunitConfigDefaultKey = "default" + AdunitConfigSlotConfigKey = "slotConfig" + AdunitConfigSlotNameKey = "slotname" + AdunitConfigSlotBannerKey = "banner" + AdunitConfigSlotVideoKey = "video" + AdunitConfigEnabledKey = "enabled" + AdUnitConfigClientConfigKey = "clientconfig" + AdunitConfigConfigKey = "config" + AdunitConfigConfigPatternKey = "configPattern" + AdunitConfigExpKey = "exp" + AdunitConfigExtKey = "ext" + + AdunitConfigBidFloor = "bidfloor" + AdunitConfigBidFloorCur = "bidfloorcur" + AdunitConfigFloorJSON = "floors" + AdunitConfigRegex = "regex" + + OpenRTBDeviceOsIosRegexPattern string = `(ios).*` + OpenRTBDeviceOsAndroidRegexPattern string = `(android).*` + IosUARegexPattern string = `(iphone|ipad|darwin).*` + AndroidUARegexPattern string = `android.*` + MobileDeviceUARegexPattern string = `(mobi|tablet|ios).*` + ConnectedDeviceUARegexPattern string = `Roku|SMART-TV|SmartTV|AndroidTV|Android TV|AppleTV|Apple TV|VIZIO|PHILIPS|BRAVIA|PlayStation|Chromecast|ExoPlayerLib|MIBOX3|Xbox|ComcastAppPlatform|AFT|HiSmart|BeyondTV|D.*ATV|PlexTV|Xstream|MiTV|AI PONT` + + HbBuyIdPrefix = "hb_buyid_" + HbBuyIdPubmaticConstantKey = "hb_buyid_pubmatic" + PwtBuyIdPubmaticConstantKey = "pwtbuyid_pubmatic" + + SChainDBKey = "sChain" + SChainObjectDBKey = "sChainObj" + SChainKey = "schain" + SChainConfigKey = "config" + + PriceFloorURL = "jsonUrl" + FloorModuleEnabled = "floorPriceModuleEnabled" + FloorType = "floorType" + SoftFloorType = "soft" + HardFloorType = "hard" + + //include brand categories values + IncludeNoCategory = 0 + IncludeIABBranchCategory = 1 + IncludeAdServerBrandCategory = 2 + + //OpenWrap Primary AdServer DFP + OWPrimaryAdServerDFP = "DFP" + + //Prebid Primary AdServers + PrebidPrimaryAdServerFreeWheel = "freewheel" + PrebidPrimaryAdServerDFP = "dfp" + + //Prebid Primary AdServer ID's + PrebidPrimaryAdServerFreeWheelID = 1 + PrebidPrimaryAdServerDFPID = 2 + + //ab test constants + AbTestEnabled = "abTestEnabled" + TestGroupSize = "testGroupSize" + TestType = "testType" + PartnerTestEnabledKey = "testEnabled" + TestTypeAuctionTimeout = "Auction Timeout" + TestTypePartners = "Partners" + TestTypeClientVsServerPath = "Client-side vs. Server-side Path" + + DataTypeUnknown = 0 + DataTypeInteger = 1 + DataTypeFloat = 2 + DataTypeString = 3 + DataTypeBoolean = 4 + DataTypeArrayOfIntegers = 5 + DataTypeArrayOfFloats = 6 + DataTypeArrayOfStrings = 7 + + Device = "device" + DeviceType = "deviceType" + + //constant for native tracker + EventTrackers = "eventtrackers" + ImpTrackers = "imptrackers" + Event = "event" + Methods = "methods" + EventValue = "1" + MethodValue = "1" + + //constants for Universal Pixel + PixelTypeUrl = "url" + PixelTypeJS = "js" + PixelPosAbove = "above" + PixelPosBelow = "below" + + //floor types + SoftFloor = 0 + HardFloor = 1 +) + +const ( + MACRO_WIDTH = "_W_" + MACRO_HEIGHT = "_H_" + MACRO_AD_UNIT_ID = "_AU_" + MACRO_AD_UNIT_INDEX = "_AUI_" + MACRO_INTEGER = "_I_" + MACRO_DIV = "_DIV_" + MACRO_SOURCE = "_SRC_" + MACRO_VASTTAG = "_VASTTAG_" + + ADUNIT_SIZE_KGP = "_AU_@_W_x_H_" + REGEX_KGP = "_AU_@_DIV_@_W_x_H_" + DIV_SIZE_KGP = "_DIV_@_W_x_H_" + ADUNIT_SOURCE_VASTTAG_KGP = "_AU_@_SRC_@_VASTTAG_" + SIZE_KGP = "_W_x_H_@_W_x_H_" +) + +var ( + //EmptyVASTResponse Empty VAST Response + EmptyVASTResponse = []byte(``) + //EmptyString to check for empty value + EmptyString = "" + //HeaderOpenWrapStatus Status of OW Request + HeaderOpenWrapStatus = "X-Ow-Status" + //ErrorFormat parsing error format + ErrorFormat = `{"` + ERROR_CODE + `":%v,"` + ERROR_STRING + `":"%s"}` + //ContentType HTTP Response Header Content Type + ContentType = `Content-Type` + //ContentTypeApplicationJSON HTTP Header Content-Type Value + ContentTypeApplicationJSON = `application/json` + //ContentTypeApplicationXML HTTP Header Content-Type Value + ContentTypeApplicationXML = `application/xml` + //EmptyJSONResponse Empty JSON Response + EmptyJSONResponse = []byte{} + //VASTErrorResponse VAST Error Response + VASTErrorResponse = `%v` + //TrackerCallWrap + TrackerCallWrap = `
` + //Tracker Format for Native + NativeTrackerMacro = `{"event":1,"method":1,"url":"${trackerUrl}"}` + //TrackerCallWrapOMActive for Open Measurement in In-App Banner + TrackerCallWrapOMActive = `` + //Universal Pixel Macro + UniversalPixelMacroForUrl = `
` +) + +// LogOnlyWinBidArr is an array containing Partners who only want winning bids to be logged +var LogOnlyWinBidArr = []string{"facebook"} + +// contextKey will be used to pass the object through request.Context +type contextKey string + +const ( + ContextOWLoggerKey contextKey = "owlogger" +) + +const ( + Pipe = "|" + BidIdSeparator = "::" +) + +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" +) + +const ( + PartnerErrNoBid = "no_bid" + PartnerErrTimeout = "timeout" + PartnerErrUnknownPrebidError = "unknown" +) + +// enum to report the error at partner-config level +const ( + PartnerErrSlotNotMapped = iota // 0 + PartnerErrMisConfig //1 +) + +// constants for query_type label in stats +const ( + PartnerConfigQuery = "GetPartnerConfig" + WrapperSlotMappingsQuery = "GetWrapperSlotMappingsQuery" + WrapperLiveVersionSlotMappings = "GetWrapperLiveVersionSlotMappings" + AdunitConfigQuery = "GetAdunitConfigQuery" + AdunitConfigForLiveVersion = "GetAdunitConfigForLiveVersion" + SlotNameHash = "GetSlotNameHash" + PublisherVASTTagsQuery = "GetPublisherVASTTagsQuery" + AllFscDisabledPublishersQuery = "GetAllFscDisabledPublishersQuery" + AllDspFscPcntQuery = "GetAllDspFscPcntQuery" + AdUnitFailUnmarshal = "GetAdUnitUnmarshal" + //DisplayVersionInnerQuery = "DisplayVersionInnerQuery" + //LiveVersionInnerQuery = "LiveVersionInnerQuery" + //PMSlotToMappings = "GetPMSlotToMappings" +) + +// constants for owlogger Integration Type +const ( + TypeTag = "tag" + TypeInline = "inline" + TypeAmp = "amp" + TypeSDK = "sdk" + TypeS2S = "s2s" + TypeWebS2S = "webs2s" +) + +// constants to accept request-test value +type testValue = int8 + +const ( + TestValueTwo testValue = 2 +) + +const ( + Success = "success" + Failure = "failure" +) + +// constants for imp.Ext.Data fields +const ( + Pbadslot = "pbadslot" + GamAdServer = "gam" +) diff --git a/modules/pubmatic/openwrap/models/db.go b/modules/pubmatic/openwrap/models/db.go new file mode 100644 index 00000000000..119ca6bbe67 --- /dev/null +++ b/modules/pubmatic/openwrap/models/db.go @@ -0,0 +1,55 @@ +package models + +import "strings" + +// VASTTag contains tag details of VASTBidders +type VASTTag struct { + ID int `json:"id,omitempty"` + PartnerID int `json:"partnerId,omitempty"` + URL string `json:"url,omitempty"` + Duration int `json:"dur,omitempty"` + Price float64 `json:"price,omitempty"` +} + +// PublisherVASTTags holds publisher level vast tag entries +type PublisherVASTTags = map[int]*VASTTag + +/*SlotMappingInfo contains the ordered list of slot names and a map of slot names to their hash values*/ +type SlotMappingInfo struct { + OrderedSlotList []string + HashValueMap map[string]string +} + +type SlotInfo struct { + SlotName string + AdSize string + AdWidth int + AdHeight int + SiteId int + AdTagId int + GId int // Gauanteed Id + Floor float64 +} + +/*SlotMapping object contains information for a given slot*/ +type SlotMapping struct { + PartnerId int64 + AdapterId int64 + VersionId int64 + SlotName string + MappingJson string + SlotMappings map[string]interface{} + Hash string + OrderID int64 +} + +type BySlotName []*SlotInfo + +func (t BySlotName) Len() int { return len(t) } +func (t BySlotName) Compare(i int, element interface{}) int { + slotname := element.(string) + return strings.Compare(t[i].SlotName, slotname) +} + +// AdUnitConfig type definition for Ad Unit config parsed from stored config JSON +type AdUnitConfig map[string]interface{} diff --git a/modules/pubmatic/openwrap/models/device.go b/modules/pubmatic/openwrap/models/device.go new file mode 100644 index 00000000000..d62d2a33934 --- /dev/null +++ b/modules/pubmatic/openwrap/models/device.go @@ -0,0 +1,63 @@ +package models + +import "github.com/prebid/prebid-server/v2/openrtb_ext" + +const ( + //Device.DeviceType values as per OpenRTB-API-Specification-Version-2-5 + DeviceTypeMobile = 1 + DeviceTypePersonalComputer = 2 + DeviceTypeConnectedTv = 3 + DeviceTypePhone = 4 + DeviceTypeTablet = 5 + DeviceTypeConnectedDevice = 6 + DeviceTypeSetTopBox = 7 +) + +// DevicePlatform defines enums as per int values from KomliAdServer.platform table +type DevicePlatform int + +const ( + DevicePlatformUnknown DevicePlatform = -1 + DevicePlatformDesktop DevicePlatform = 1 //Desktop Web + DevicePlatformMobileWeb DevicePlatform = 2 //Mobile Web + DevicePlatformNotDefined DevicePlatform = 3 + DevicePlatformMobileAppIos DevicePlatform = 4 //In-App iOS + DevicePlatformMobileAppAndroid DevicePlatform = 5 //In-App Android + DevicePlatformMobileAppWindows DevicePlatform = 6 + DevicePlatformConnectedTv DevicePlatform = 7 //Connected TV +) + +// DeviceIFAType defines respective logger int id for device type +type DeviceIFAType = int + +// DeviceIFATypeID +var DeviceIFATypeID = map[string]DeviceIFAType{ + DeviceIFATypeDPID: 1, + DeviceIFATypeRIDA: 2, + DeviceIFATypeAAID: 3, + DeviceIFATypeIDFA: 4, + DeviceIFATypeAFAI: 5, + DeviceIFATypeMSAI: 6, + DeviceIFATypePPID: 7, + DeviceIFATypeSSPID: 8, + DeviceIFATypeSESSIONID: 9, +} + +// Device Ifa type constants +const ( + DeviceIFATypeDPID = "dpid" + DeviceIFATypeRIDA = "rida" + DeviceIFATypeAAID = "aaid" + DeviceIFATypeIDFA = "idfa" + DeviceIFATypeAFAI = "afai" + DeviceIFATypeMSAI = "msai" + DeviceIFATypePPID = "ppid" + DeviceIFATypeSSPID = "sspid" + DeviceIFATypeSESSIONID = "sessionid" +) + +type ExtDevice struct { + *openrtb_ext.ExtDevice + SessionID string `json:"session_id,omitempty"` + IDFV string `json:"idfv,omitempty"` +} diff --git a/modules/pubmatic/openwrap/models/gocommon.go b/modules/pubmatic/openwrap/models/gocommon.go new file mode 100644 index 00000000000..3b868ba82fe --- /dev/null +++ b/modules/pubmatic/openwrap/models/gocommon.go @@ -0,0 +1,393 @@ +package models + +import ( + "net/url" +) + +type RequestAPI int + +const ( + ADMIN_API RequestAPI = iota + GADS_API + OpenRTB_V23_API + OpenRTB_V24_API + OpenRTB_V241_API + OpenRTB_V25_API + OpenRTB_AMP_API + OpenRTB_VIDEO_API + OpenRTB_VIDEO_OPENRTB_API + OpenRTB_VIDEO_VAST_API + OpenRTB_VIDEO_JSON_API +) + +const ( + SLOT_KEY = "slot" + KEY_VALUE_KEY = "keyValue" + ID_KEY = "id" + DIV_KEY = "div" + SLOT_INDEX_KEY = "slotIndex" + PROFILE_KEY = "profileid" + GA_ID_KEY = "gaId" + SITE_ID = "siteId" + ADTAG_ID = "adTagId" + BID_REQUEST_ID = "bidRequestId" + IMPRESSION_ID = "impId" + DM_KEY = "dm" + AS_KEY = "as" + WRAPPER_KEY = "wrapper" + ADPOD_KEY = "adpod" + BIDDER_KEY = "bidder" + RESPONSE_TYPE_KEY = "rs" + PM_CB_KEY = "pm_cb" + SERVER_SIDE_AUCTION_FLAG = "ssauction" + SUMMERY_DISABLE_FLAG = "sumry_disable" + KVAL_PARAM_KEY = "kval_param" + SA_VERSION_KEY = "SAVersion" + PAGE_URL_KEY = "pageURL" + REF_URL_KEY = "refurl" + IN_IFRAME_KEY = "inIframe" + KAD_PAGE_URL_KEY = "kadpageurl" + RAN_REQ_KEY = "ranreq" + KLT_STAMP_KEY = "kltstamp" + TIMEZONE_KEY = "timezone" + SCREEN_RESOLUTION_KEY = "screenResolution" + ADTYPE_KEY = "adType" + ADPOSITION_KEY = "adPosition" + ADVISIBILITY_KEY = "adVisibility" + IABCAT_KEY = "iabcat" + AWT_KEY = "awt" + ZONEID_KEY = "pmZoneId" + SITECODE_KEY = "sitecode" + UDID_KEY = "udid" + UDID_TYPE_KEY = "udidtype" + UDID_HASH_KEY = "udidhash" + ORMMA_KEY = "ormma" + AD_ORIENTATION_KEY = "adOrientation" + DEVICE_ORIENTATION_KEY = "deviceOrientation" + LOCCAT_KEY = "loccat" + LOCBRAND_KEY = "locbrand" + KADFLOOR_KEY = "kadfloor" + RID_KEY = "rid" + LOC_SOURCE_KEY = "loc_source" + ETHN_KEY = "ethn" + KEYWORDS_KEY = "keywords" + LOC_KEY = "loc" + CAT_KEY = "cat" + API_KEY = "api" + NETTYPE_KEY = "nettype" + CONSENT = "consent" + GET_METHOD_QUERY_PARAM = "json" + PAGE_URL_HEADER = "Referer" + SKAdnetworkKey = "skadn" + OmidpvKey = "omidpv" + OmidpnKey = "omidpn" + RewardKey = "reward" + DataKey = "data" + DEAL_TIER_KEY = "dealtier" + FluidStr = "fluid" + DeviceSessionID = "session_id" + DeviceIfaType = "ifa_type" +) + +type ResponseType int + +const ( + ORTB_RESPONSE ResponseType = 1 + iota //openRTB default response + GADS_RESPONSE_1 //gshow ad response + GADS_RESPONSE_2 //DM gpt generic response +) + +const ( + // USD denotes currency USD + USD = "USD" +) + +// constants related to Video request +const ( + PlayerSizeKey = "sz" + SizeStringSeparator = "x" + DescriptionURLKey = "description_url" + URLKey = "url" + MimesSeparator = "," + MultipleSizeSeparator = "|" + AppRequestURLKey = "pwtapp" + Comma = "," +) + +// OpenWrap Video request params +const ( + OWMimeTypes = "pwtmime" + OWMinAdDuration = "pwtvmnd" + OWMaxAdDuration = "pwtvmxd" + OWStartDelay = "pwtdly" + OWPlaybackMethod = "pwtplbk" + OWAPI = "pwtvapi" + OWProtocols = "pwtprots" + OWSize = "pwtvsz" + OWBAttr = "pwtbatr" + OWLinearity = "pwtvlin" + OWPlacement = "pwtvplc" + OWMaxBitrate = "pwtmxbr" + OWMinBitrate = "pwtmnbr" + OWSkippable = "pwtskp" + OWProtocol = "pwtprot" + OWSkipMin = "pwtskmn" + OWSkipAfter = "pwtskat" + OWSequence = "pwtseq" + OWMaxExtended = "pwtmxex" + OWDelivery = "pwtdvry" + OWPos = "pwtvpos" + OWBoxingAllowed = "pwtbox" + OWBidderParams = "pwtbidrprm" + OwAppKeywords = "pwtappkw" + OWUserEids = "pwteids" +) + +// OpenWrap Mobile params +const ( + OWAppId = "pwtappid" + OWAppName = "pwtappname" + OWAppDomain = "pwtappdom" + OWAppBundle = "pwtappbdl" + OWAppStoreURL = "pwtappurl" + OWAppCat = "pwtappcat" + OWAppPaid = "pwtapppd" + OWDeviceUA = "pwtua" + OWDeviceLMT = "pwtlmt" + OWDeviceDNT = "pwtdnt" + OWDeviceIP = "pwtip" + OWDeviceJS = "pwtjs" + OWDeviceIfa = "pwtifa" + OWDeviceDidsha1 = "pwtdidsha1" + OWDeviceDidmd5 = "pwtdidmd5" + OWDeviceDpidsha1 = "pwtdpidsha1" + OWDeviceDpidmd5 = "pwtdpidmd5" + OWDeviceMacsha1 = "pwtmacsha1" + OWDeviceMacmd5 = "pwtmacmd5" + OWUserID = "pwtuid" + OWGeoLat = "pwtlat" + OWGeoLon = "pwtlon" + OWGeoType = "pwtgtype" + OWGeoCountry = "pwtcntr" + OWGeoCity = "pwtcity" + OWGeoMetro = "pwtmet" + OWGeoZip = "pwtzip" + OWUTOffset = "pwtuto" + OWContentGenre = "pwtgenre" + OWContentTitle = "pwttitle" + OWUserYob = "pwtyob" + OWUserGender = "pwtgender" + OWSourceOmidPv = "pwtomidpv" + OWSourceOmidPn = "pwtomidpn" + OWDeviceExtIfaType = "pwtifatype" + OWDeviceExtSessionID = "pwtsessionid" + OWImpPrebidExt = "pwtimpprebidext" +) + +// constants for DFP Video request parameters +const ( + DFPMinAdDuration = "min_ad_duration" + DFPMaxAdDuration = "max_ad_duration" + DFPSize = PlayerSizeKey + DFPVAdType = "vad_type" + DFPVPos = "vpos" + DFPVpmute = "vpmute" + DFPVpa = "vpa" +) + +// constants for oRTB Request Video parameters +const ( + MimeORTBParam = "Mimes" + MinDurationORTBParam = "MinDuration" + MaxDurationORTBParam = "MaxDuration" + ProtocolsORTBParam = "Protocols" + ProtocolORTBParam = "Protocol" + WORTBParam = "W" + HORTBParam = "H" + SizeORTBParam = "sz" + StartDelayORTBParam = "StartDelay" + PlacementORTBParam = "Placement" + LinearityORTBParam = "Linearity" + SkipORTBParam = "Skip" + SkipMinORTBParam = "SkipMin" + SkipAfterORTBParam = "SkipAfter" + SequenceORTBParam = "Sequence" + BAttrORTBParam = "BAttr" + MaxExtendedORTBParam = "MaxExtended" + MinBitrateORTBParam = "MinBitrate" + MaxBitrateORTBParam = "MaxBitrate" + BoxingAllowedORTBParam = "BoxingAllowed" + PlaybackMethodORTBParam = "PlaybackMethod" + DeliveryORTBParam = "Delivery" + PosORTBParam = "Pos" + CompanionadORTBParam = "Companionad" + APIORTBParam = "API" + CompanionTypeORTBParam = "CompanionType" + AppIDORTBParam = "AppID" + AppNameORTBParam = "AppName" + AppBundleORTBParam = "AppBundle" + AppStoreURLORTBParam = "AppStoreURL" + AppDomainORTBParam = "AppDomain" + AppCatORTBParam = "AppCat" + AppPaidORTBParam = "AppPaid" + DeviceUAORTBParam = "DeviceUA" + DeviceDNTORTBParam = "DeviceDNT" + DeviceLMTORTBParam = "DeviceLMT" + DeviceJSORTBParam = "DeviceJS" + DeviceIPORTBParam = "DeviceIP" + DeviceIfaORTBParam = "DeviceIfa" + DeviceDidsha1ORTBParam = "DeviceDidsha1" + DeviceDidmd5ORTBParam = "DeviceDidmd5" + DeviceDpidsha1ORTBParam = "DeviceDpidsha1" + DeviceDpidmd5ORTBParam = "DeviceDpidmd5" + DeviceMacsha1ORTBParam = "DeviceMacsha1" + DeviceMacmd5ORTBParam = "DeviceMacmd5" + GeoLatORTBParam = "GeoLat" + GeoLonORTBParam = "GeoLon" + GeoTypeORTBParam = "GeoType" + GeoCountryORTBParam = "GeoCountry" + GeoCityORTBParam = "GeoCity" + GeoMetroORTBParam = "GeoMetro" + GeoZipORTBParam = "GeoZip" + GeoUTOffsetORTBParam = "GeoUTOffset" + UserIDORTBParam = "UserId" + UserYobORTBParam = "UserYob" + UserGenderORTBParam = "UserGender" + SourceOmidpvORTBParam = "SourceOmidpv" + SourceOmidpnORTBParam = "SourceOmidpn" + ContentGenreORTBParam = "Genre" + ContentTitleORTBParam = "Title" + BidderParams = "BidderParams" + DeviceExtSessionID = "DeviceExtSessionID" + DeviceExtIfaType = "DeviceExtIfaType" + ImpPrebidExt = "ImpPrebidExt" +) + +// ORTBToDFPOWMap is Map of ORTB params to DFP and OW params. 0th position in map value denotes DFP param and 1st position in value denotes OW param. To populate a given ORTB parameter, preference would be given to DFP value and if its not present, OW value would be used +var ORTBToDFPOWMap = map[string][]string{ + MimeORTBParam: {OWMimeTypes, ""}, + MinDurationORTBParam: {OWMinAdDuration, DFPMinAdDuration}, + MaxDurationORTBParam: {OWMaxAdDuration, DFPMaxAdDuration}, + StartDelayORTBParam: {OWStartDelay, DFPVPos}, + PlaybackMethodORTBParam: {OWPlaybackMethod, ""}, + APIORTBParam: {OWAPI, ""}, + ProtocolsORTBParam: {OWProtocols, ""}, + SizeORTBParam: {OWSize, DFPSize}, + BAttrORTBParam: {OWBAttr, ""}, + LinearityORTBParam: {OWLinearity, DFPVAdType}, + PlacementORTBParam: {OWPlacement, ""}, + MaxBitrateORTBParam: {OWMaxBitrate, ""}, + MinBitrateORTBParam: {OWMinBitrate, ""}, + SkipORTBParam: {OWSkippable, ""}, + SkipMinORTBParam: {OWSkipMin, ""}, + SkipAfterORTBParam: {OWSkipAfter, ""}, + ProtocolORTBParam: {OWProtocol, ""}, + SequenceORTBParam: {OWSequence, ""}, + MaxExtendedORTBParam: {OWMaxExtended, ""}, + BoxingAllowedORTBParam: {OWBoxingAllowed, ""}, + DeliveryORTBParam: {OWDelivery, ""}, + PosORTBParam: {OWPos, ""}, + AppIDORTBParam: {OWAppId, ""}, + AppNameORTBParam: {OWAppName, ""}, + AppBundleORTBParam: {OWAppBundle, ""}, + AppStoreURLORTBParam: {OWAppStoreURL, ""}, + AppCatORTBParam: {OWAppCat, ""}, + AppPaidORTBParam: {OWAppPaid, ""}, + AppDomainORTBParam: {OWAppDomain, ""}, + DeviceUAORTBParam: {OWDeviceUA, ""}, + DeviceDNTORTBParam: {OWDeviceDNT, ""}, + DeviceLMTORTBParam: {OWDeviceLMT, ""}, + DeviceJSORTBParam: {OWDeviceJS, ""}, + DeviceIPORTBParam: {OWDeviceIP, ""}, + DeviceIfaORTBParam: {OWDeviceIfa, ""}, + DeviceDidsha1ORTBParam: {OWDeviceDidsha1, ""}, + DeviceDidmd5ORTBParam: {OWDeviceDidmd5, ""}, + DeviceDpidsha1ORTBParam: {OWDeviceDpidsha1, ""}, + DeviceDpidmd5ORTBParam: {OWDeviceDpidmd5, ""}, + DeviceMacsha1ORTBParam: {OWDeviceMacsha1, ""}, + DeviceMacmd5ORTBParam: {OWDeviceMacmd5, ""}, + GeoLatORTBParam: {OWGeoLat, ""}, + GeoLonORTBParam: {OWGeoLon, ""}, + GeoTypeORTBParam: {OWGeoType, ""}, + UserIDORTBParam: {OWUserID, ""}, + GeoCountryORTBParam: {OWGeoCountry, ""}, + GeoCityORTBParam: {OWGeoCity, ""}, + GeoMetroORTBParam: {OWGeoMetro, ""}, + GeoZipORTBParam: {OWGeoZip, ""}, + GeoUTOffsetORTBParam: {OWUTOffset, ""}, + ContentGenreORTBParam: {OWContentGenre, ""}, + ContentTitleORTBParam: {OWContentTitle, ""}, + UserYobORTBParam: {OWUserYob, ""}, + UserGenderORTBParam: {OWUserGender, ""}, + SourceOmidpvORTBParam: {OWSourceOmidPv, ""}, + SourceOmidpnORTBParam: {OWSourceOmidPn, ""}, + BidderParams: {OWBidderParams, ""}, + DeviceExtSessionID: {OWDeviceExtSessionID, ""}, + DeviceExtIfaType: {OWDeviceExtIfaType, ""}, + ImpPrebidExt: {OWImpPrebidExt, ""}, +} + +// DFP Video positions constants +const ( + Preroll = "preroll" + Midroll = "midroll" + Postroll = "postroll" +) + +// VideoPositionToStartDelayMap is a map of DFP Video positions to Start Delay integer values in oRTB request +var VideoPositionToStartDelayMap = map[string]string{ + Preroll: "0", + Midroll: "-1", + Postroll: "-2", +} + +// DFP Video linearity (vad_type) constants +const ( + Linear = "linear" + Nonlinear = "nonlinear" +) + +// LinearityMap is a map of DFP Linearity values to oRTB values +var LinearityMap = map[string]string{ + Linear: "1", + Nonlinear: "2", +} + +// Mime types +const ( + All = "0" + VideoMP4 = "1" // video/mp4 + VPAIDFlash = "2" // application/x-shockwave-flash (VPAID - FLASH) + VideoWMV = "3" // video/wmv + VideoH264 = "4" // video/h264 + VideoWebm = "5" // video/webm + VPAIDJS = "6" // application/javascript (VPAID - JS) + VideoOGG = "7" // video/ogg + VideoFLV = "8" // video/flv (Flash Video) +) + +// MimeIDToValueMap is a map of Mime IDs to string values +var MimeIDToValueMap = map[string]string{ + All: "All", + VideoMP4: "video/mp4", + VPAIDFlash: "application/x-shockwave-flash", + VideoWMV: "video/wmv", + VideoH264: "video/h264", + VideoWebm: "video/webm", + VPAIDJS: "application/javascript", + VideoOGG: "video/ogg", + VideoFLV: "video/flv", +} + +// CheckIfValidQueryParamFlag checks if given query parameter has a valid flag value(i.e. 0 or 1) +func CheckIfValidQueryParamFlag(values url.Values, key string) bool { + validationFailed := false + paramValue := values.Get(key) + if paramValue == "" { + return validationFailed + } + if paramValue != "0" && paramValue != "1" { + validationFailed = true + } + return validationFailed +} diff --git a/modules/pubmatic/openwrap/models/iso6391.go b/modules/pubmatic/openwrap/models/iso6391.go new file mode 100644 index 00000000000..1f22b074612 --- /dev/null +++ b/modules/pubmatic/openwrap/models/iso6391.go @@ -0,0 +1,203 @@ +package models + +// Language is an ISO 639-1 language with code, name and native name. +type Language struct { + Code string + Name string + NativeName string +} + +// Languages is a map of all ISO 639-1 languages using the two character lowercase language code as key. +var Languages = map[string]Language{ + "ls": {Code: "aa", Name: "Afar", NativeName: "Afaraf"}, + "ab": {Code: "ab", Name: "Abkhaz", NativeName: "аҧсуа бызшәа"}, + "ae": {Code: "ae", Name: "Avestan", NativeName: "avesta"}, + "af": {Code: "af", Name: "Afrikaans", NativeName: "Afrikaans"}, + "ak": {Code: "ak", Name: "Akan", NativeName: "Akan"}, + "am": {Code: "am", Name: "Amharic", NativeName: "አማርኛ"}, + "an": {Code: "an", Name: "Aragonese", NativeName: "aragonés"}, + "ar": {Code: "ar", Name: "Arabic", NativeName: "اللغة العربية"}, + "as": {Code: "as", Name: "Assamese", NativeName: "অসমীয়া"}, + "av": {Code: "av", Name: "Avaric", NativeName: "авар мацӀ"}, + "ay": {Code: "ay", Name: "Aymara", NativeName: "aymar aru"}, + "az": {Code: "az", Name: "Azerbaijani", NativeName: "azərbaycan dili"}, + "ba": {Code: "ba", Name: "Bashkir", NativeName: "башҡорт теле"}, + "be": {Code: "be", Name: "Belarusian", NativeName: "беларуская мова"}, + "bg": {Code: "bg", Name: "Bulgarian", NativeName: "български език"}, + "bh": {Code: "bh", Name: "Bihari", NativeName: "भोजपुरी"}, + "bi": {Code: "bi", Name: "Bislama", NativeName: "Bislama"}, + "bm": {Code: "bm", Name: "Bambara", NativeName: "bamanankan"}, + "bn": {Code: "bn", Name: "Bengali", NativeName: "বাংলা"}, + "bo": {Code: "bo", Name: "Tibetan Standard", NativeName: "བོད་ཡིག"}, + "br": {Code: "br", Name: "Breton", NativeName: "brezhoneg"}, + "bs": {Code: "bs", Name: "Bosnian", NativeName: "bosanski jezik"}, + "ca": {Code: "ca", Name: "Catalan", NativeName: "català"}, + "ce": {Code: "ce", Name: "Chechen", NativeName: "нохчийн мотт"}, + "ch": {Code: "ch", Name: "Chamorro", NativeName: "Chamoru"}, + "co": {Code: "co", Name: "Corsican", NativeName: "corsu"}, + "cr": {Code: "cr", Name: "Cree", NativeName: "ᓀᐦᐃᔭᐍᐏᐣ"}, + "cs": {Code: "cs", Name: "Czech", NativeName: "čeština"}, + "cu": {Code: "cu", Name: "Old Church Slavonic", NativeName: "ѩзыкъ словѣньскъ"}, + "cv": {Code: "cv", Name: "Chuvash", NativeName: "чӑваш чӗлхи"}, + "cy": {Code: "cy", Name: "Welsh", NativeName: "Cymraeg"}, + "da": {Code: "da", Name: "Danish", NativeName: "dansk"}, + "de": {Code: "de", Name: "German", NativeName: "Deutsch"}, + "dv": {Code: "dv", Name: "Divehi", NativeName: "Dhivehi"}, + "dz": {Code: "dz", Name: "Dzongkha", NativeName: "རྫོང་ཁ"}, + "ee": {Code: "ee", Name: "Ewe", NativeName: "Eʋegbe"}, + "el": {Code: "el", Name: "Greek", NativeName: "Ελληνικά"}, + "en": {Code: "en", Name: "English", NativeName: "English"}, + "eo": {Code: "eo", Name: "Esperanto", NativeName: "Esperanto"}, + "es": {Code: "es", Name: "Spanish", NativeName: "Español"}, + "et": {Code: "et", Name: "Estonian", NativeName: "eesti"}, + "eu": {Code: "eu", Name: "Basque", NativeName: "euskara"}, + "fa": {Code: "fa", Name: "Persian", NativeName: "فارسی"}, + "ff": {Code: "ff", Name: "Fula", NativeName: "Fulfulde"}, + "fi": {Code: "fi", Name: "Finnish", NativeName: "suomi"}, + "fj": {Code: "fj", Name: "Fijian", NativeName: "Vakaviti"}, + "fo": {Code: "fo", Name: "Faroese", NativeName: "føroyskt"}, + "fr": {Code: "fr", Name: "French", NativeName: "Français"}, + "fy": {Code: "fy", Name: "Western Frisian", NativeName: "Frysk"}, + "ga": {Code: "ga", Name: "Irish", NativeName: "Gaeilge"}, + "gd": {Code: "gd", Name: "Scottish Gaelic", NativeName: "Gàidhlig"}, + "gl": {Code: "gl", Name: "Galician", NativeName: "galego"}, + "gn": {Code: "gn", Name: "Guaraní", NativeName: "Avañeẽ"}, + "gu": {Code: "gu", Name: "Gujarati", NativeName: "ગુજરાતી"}, + "gv": {Code: "gv", Name: "Manx", NativeName: "Gaelg"}, + "ha": {Code: "ha", Name: "Hausa", NativeName: "هَوُسَ"}, + "he": {Code: "he", Name: "Hebrew", NativeName: "עברית"}, + "hi": {Code: "hi", Name: "Hindi", NativeName: "हिन्दी"}, + "ho": {Code: "ho", Name: "Hiri Motu", NativeName: "Hiri Motu"}, + "hr": {Code: "hr", Name: "Croatian", NativeName: "hrvatski jezik"}, + "ht": {Code: "ht", Name: "Haitian", NativeName: "Kreyòl ayisyen"}, + "hu": {Code: "hu", Name: "Hungarian", NativeName: "magyar"}, + "hy": {Code: "hy", Name: "Armenian", NativeName: "Հայերեն"}, + "hz": {Code: "hz", Name: "Herero", NativeName: "Otjiherero"}, + "ia": {Code: "ia", Name: "Interlingua", NativeName: "Interlingua"}, + "id": {Code: "id", Name: "Indonesian", NativeName: "Indonesian"}, + "ie": {Code: "ie", Name: "Interlingue", NativeName: "Interlingue"}, + "ig": {Code: "ig", Name: "Igbo", NativeName: "Asụsụ Igbo"}, + "ii": {Code: "ii", Name: "Nuosu", NativeName: "ꆈꌠ꒿ Nuosuhxop"}, + "ik": {Code: "ik", Name: "Inupiaq", NativeName: "Iñupiaq"}, + "io": {Code: "io", Name: "Ido", NativeName: "Ido"}, + "is": {Code: "is", Name: "Icelandic", NativeName: "Íslenska"}, + "it": {Code: "it", Name: "Italian", NativeName: "Italiano"}, + "iu": {Code: "iu", Name: "Inuktitut", NativeName: "ᐃᓄᒃᑎᑐᑦ"}, + "ja": {Code: "ja", Name: "Japanese", NativeName: "日本語"}, + "jv": {Code: "jv", Name: "Javanese", NativeName: "basa Jawa"}, + "ka": {Code: "ka", Name: "Georgian", NativeName: "ქართული"}, + "kg": {Code: "kg", Name: "Kongo", NativeName: "Kikongo"}, + "ki": {Code: "ki", Name: "Kikuyu", NativeName: "Gĩkũyũ"}, + "kj": {Code: "kj", Name: "Kwanyama", NativeName: "Kuanyama"}, + "kk": {Code: "kk", Name: "Kazakh", NativeName: "қазақ тілі"}, + "kl": {Code: "kl", Name: "Kalaallisut", NativeName: "kalaallisut"}, + "km": {Code: "km", Name: "Khmer", NativeName: "ខេមរភាសា"}, + "kn": {Code: "kn", Name: "Kannada", NativeName: "ಕನ್ನಡ"}, + "ko": {Code: "ko", Name: "Korean", NativeName: "한국어"}, + "kr": {Code: "kr", Name: "Kanuri", NativeName: "Kanuri"}, + "ks": {Code: "ks", Name: "Kashmiri", NativeName: "कश्मीरी"}, + "ku": {Code: "ku", Name: "Kurdish", NativeName: "Kurdî"}, + "kv": {Code: "kv", Name: "Komi", NativeName: "коми кыв"}, + "kw": {Code: "kw", Name: "Cornish", NativeName: "Kernewek"}, + "ky": {Code: "ky", Name: "Kyrgyz", NativeName: "Кыргызча"}, + "la": {Code: "la", Name: "Latin", NativeName: "latine"}, + "lb": {Code: "lb", Name: "Luxembourgish", NativeName: "Lëtzebuergesch"}, + "lg": {Code: "lg", Name: "Ganda", NativeName: "Luganda"}, + "li": {Code: "li", Name: "Limburgish", NativeName: "Limburgs"}, + "ln": {Code: "ln", Name: "Lingala", NativeName: "Lingála"}, + "lo": {Code: "lo", Name: "Lao", NativeName: "ພາສາ"}, + "lt": {Code: "lt", Name: "Lithuanian", NativeName: "lietuvių kalba"}, + "lu": {Code: "lu", Name: "Luba-Katanga", NativeName: "Tshiluba"}, + "lv": {Code: "lv", Name: "Latvian", NativeName: "latviešu valoda"}, + "mg": {Code: "mg", Name: "Malagasy", NativeName: "fiteny malagasy"}, + "mh": {Code: "mh", Name: "Marshallese", NativeName: "Kajin M̧ajeļ"}, + "mi": {Code: "mi", Name: "Māori", NativeName: "te reo Māori"}, + "mk": {Code: "mk", Name: "Macedonian", NativeName: "македонски јазик"}, + "ml": {Code: "ml", Name: "Malayalam", NativeName: "മലയാളം"}, + "mn": {Code: "mn", Name: "Mongolian", NativeName: "Монгол хэл"}, + "mr": {Code: "mr", Name: "Marathi", NativeName: "मराठी"}, + "ms": {Code: "ms", Name: "Malay", NativeName: "هاس ملايو\u200e"}, + "mt": {Code: "mt", Name: "Maltese", NativeName: "Malti"}, + "my": {Code: "my", Name: "Burmese", NativeName: "ဗမာစာ"}, + "na": {Code: "na", Name: "Nauru", NativeName: "Ekakairũ Naoero"}, + "nb": {Code: "nb", Name: "Norwegian Bokmål", NativeName: "Norsk bokmål"}, + "nd": {Code: "nd", Name: "Northern Ndebele", NativeName: "isiNdebele"}, + "ne": {Code: "ne", Name: "Nepali", NativeName: "नेपाली"}, + "ng": {Code: "ng", Name: "Ndonga", NativeName: "Owambo"}, + "nl": {Code: "nl", Name: "Dutch", NativeName: "Nederlands"}, + "nn": {Code: "nn", Name: "Norwegian Nynorsk", NativeName: "Norsk nynorsk"}, + "no": {Code: "no", Name: "Norwegian", NativeName: "Norsk"}, + "nr": {Code: "nr", Name: "Southern Ndebele", NativeName: "isiNdebele"}, + "nv": {Code: "nv", Name: "Navajo", NativeName: "Diné bizaad"}, + "ny": {Code: "ny", Name: "Chichewa", NativeName: "chiCheŵa"}, + "oc": {Code: "oc", Name: "Occitan", NativeName: "occitan"}, + "oj": {Code: "oj", Name: "Ojibwe", NativeName: "ᐊᓂᔑᓈᐯᒧᐎᓐ"}, + "om": {Code: "om", Name: "Oromo", NativeName: "Afaan Oromoo"}, + "or": {Code: "or", Name: "Oriya", NativeName: "ଓଡ଼ିଆ"}, + "os": {Code: "os", Name: "Ossetian", NativeName: "ирон æвзаг"}, + "pa": {Code: "pa", Name: "Panjabi", NativeName: "ਪੰਜਾਬੀ"}, + "pi": {Code: "pi", Name: "Pāli", NativeName: "पाऴि"}, + "pl": {Code: "pl", Name: "Polish", NativeName: "język polski"}, + "ps": {Code: "ps", Name: "Pashto", NativeName: "پښتو"}, + "pt": {Code: "pt", Name: "Portuguese", NativeName: "Português"}, + "qu": {Code: "qu", Name: "Quechua", NativeName: "Runa Simi"}, + "rm": {Code: "rm", Name: "Romansh", NativeName: "rumantsch grischun"}, + "rn": {Code: "rn", Name: "Kirundi", NativeName: "Ikirundi"}, + "ro": {Code: "ro", Name: "Romanian", NativeName: "Română"}, + "ru": {Code: "ru", Name: "Russian", NativeName: "Русский"}, + "rw": {Code: "rw", Name: "Kinyarwanda", NativeName: "Ikinyarwanda"}, + "sa": {Code: "sa", Name: "Sanskrit", NativeName: "संस्कृतम्"}, + "sc": {Code: "sc", Name: "Sardinian", NativeName: "sardu"}, + "sd": {Code: "sd", Name: "Sindhi", NativeName: "सिन्धी"}, + "se": {Code: "se", Name: "Northern Sami", NativeName: "Davvisámegiella"}, + "sg": {Code: "sg", Name: "Sango", NativeName: "yângâ tî sängö"}, + "si": {Code: "si", Name: "Sinhala", NativeName: "සිංහල"}, + "sk": {Code: "sk", Name: "Slovak", NativeName: "slovenčina"}, + "sl": {Code: "sl", Name: "Slovene", NativeName: "slovenski jezik"}, + "sm": {Code: "sm", Name: "Samoan", NativeName: "gagana faa Samoa"}, + "sn": {Code: "sn", Name: "Shona", NativeName: "chiShona"}, + "so": {Code: "so", Name: "Somali", NativeName: "Soomaaliga"}, + "sq": {Code: "sq", Name: "Albanian", NativeName: "Shqip"}, + "sr": {Code: "sr", Name: "Serbian", NativeName: "српски језик"}, + "ss": {Code: "ss", Name: "Swati", NativeName: "SiSwati"}, + "st": {Code: "st", Name: "Southern Sotho", NativeName: "Sesotho"}, + "su": {Code: "su", Name: "Sundanese", NativeName: "Basa Sunda"}, + "sv": {Code: "sv", Name: "Swedish", NativeName: "svenska"}, + "sw": {Code: "sw", Name: "Swahili", NativeName: "Kiswahili"}, + "ta": {Code: "ta", Name: "Tamil", NativeName: "தமிழ்"}, + "te": {Code: "te", Name: "Telugu", NativeName: "తెలుగు"}, + "tg": {Code: "tg", Name: "Tajik", NativeName: "тоҷикӣ"}, + "th": {Code: "th", Name: "Thai", NativeName: "ไทย"}, + "ti": {Code: "ti", Name: "Tigrinya", NativeName: "ትግርኛ"}, + "tk": {Code: "tk", Name: "Turkmen", NativeName: "Türkmen"}, + "tl": {Code: "tl", Name: "Tagalog", NativeName: "Wikang Tagalog"}, + "tn": {Code: "tn", Name: "Tswana", NativeName: "Setswana"}, + "to": {Code: "to", Name: "Tonga", NativeName: "faka Tonga"}, + "tr": {Code: "tr", Name: "Turkish", NativeName: "Türkçe"}, + "ts": {Code: "ts", Name: "Tsonga", NativeName: "Xitsonga"}, + "tt": {Code: "tt", Name: "Tatar", NativeName: "татар теле"}, + "tw": {Code: "tw", Name: "Twi", NativeName: "Twi"}, + "ty": {Code: "ty", Name: "Tahitian", NativeName: "Reo Tahiti"}, + "ug": {Code: "ug", Name: "Uyghur", NativeName: "ئۇيغۇرچە\u200e"}, + "uk": {Code: "uk", Name: "Ukrainian", NativeName: "Українська"}, + "ur": {Code: "ur", Name: "Urdu", NativeName: "اردو"}, + "uz": {Code: "uz", Name: "Uzbek", NativeName: "Ўзбек"}, + "ve": {Code: "ve", Name: "Venda", NativeName: "Tshivenḓa"}, + "vi": {Code: "vi", Name: "Vietnamese", NativeName: "Tiếng Việt"}, + "vo": {Code: "vo", Name: "Volapük", NativeName: "Volapük"}, + "wa": {Code: "wa", Name: "Walloon", NativeName: "walon"}, + "wo": {Code: "wo", Name: "Wolof", NativeName: "Wollof"}, + "xh": {Code: "xh", Name: "Xhosa", NativeName: "isiXhosa"}, + "yi": {Code: "yi", Name: "Yiddish", NativeName: "ייִדיש"}, + "yo": {Code: "yo", Name: "Yoruba", NativeName: "Yorùbá"}, + "za": {Code: "za", Name: "Zhuang", NativeName: "Saɯ cueŋƅ"}, + "zh": {Code: "zh", Name: "Chinese", NativeName: "中文"}, + "zu": {Code: "zu", Name: "Zulu", NativeName: "isiZulu"}, +} + +// ValidCode returns true if the given code is a valid ISO 639-1 language code. +// The code must be passed in lowercase. +func ValidCode(code string) bool { + _, ok := Languages[code] + return ok +} diff --git a/modules/pubmatic/openwrap/models/nbr/codes.go b/modules/pubmatic/openwrap/models/nbr/codes.go new file mode 100644 index 00000000000..aeacbaea6d0 --- /dev/null +++ b/modules/pubmatic/openwrap/models/nbr/codes.go @@ -0,0 +1,18 @@ +package nbr + +const ( + // 500+ Vendor-specific codes. + // 5xx already in use by seat non bid. https://github.com/PubMatic-OpenWrap/prebid-openrtb/blob/main/openrtb3/non_bid_status_code.go#L53 + InvalidRequestWrapperExtension int = 601 + iota + InvalidProfileID + InvalidPublisherID + InvalidRequestExt + InvalidProfileConfiguration + InvalidPlatform + AllPartnerThrottled + InvalidPriceGranularityConfig + InvalidImpressionTagID + InternalError + AllSlotsDisabled + ServerSidePartnerNotConfigured +) diff --git a/modules/pubmatic/openwrap/models/openwrap.go b/modules/pubmatic/openwrap/models/openwrap.go new file mode 100644 index 00000000000..2a73cde743c --- /dev/null +++ b/modules/pubmatic/openwrap/models/openwrap.go @@ -0,0 +1,174 @@ +package models + +import ( + "encoding/json" + "net/http" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/usersync" +) + +type RequestCtx struct { + // PubID is the publisher id retrieved from request + PubID int + // ProfileID is the value received in profileid field in wrapper object. + ProfileID int + // DisplayID is the value received in versionid field in wrapper object. + DisplayID int + // VersionID is the unique id from DB associated with the incoming DisplayID + VersionID int + // DisplayVersionID is the DisplayID of the profile selected by OpenWrap incase DisplayID/versionid is 0 + DisplayVersionID int + + SSAuction int + SummaryDisable int + LogInfoFlag int + SSAI string + PartnerConfigMap map[int]map[string]string + SupportDeals bool + Platform string + LoggerImpressionID string + ClientConfigFlag int + + IP string + TMax int64 + + //NYC_TODO: use enum? + IsTestRequest int8 + ABTestConfig, ABTestConfigApplied int + IsCTVRequest bool + + TrackerEndpoint, VideoErrorTrackerEndpoint string + + UA string + Cookies string + UidCookie *http.Cookie + KADUSERCookie *http.Cookie + ParsedUidCookie *usersync.Cookie + OriginCookie string + + Debug bool + Trace bool + + //tracker + PageURL string + StartTime int64 + DevicePlatform DevicePlatform + + //trackers per bid + Trackers map[string]OWTracker + + //prebid-biddercode to seat/alias mapping + PrebidBidderCode map[string]string + + // imp-bid ctx to avoid computing same thing for bidder params, logger and tracker + ImpBidCtx map[string]ImpCtx + Aliases map[string]string + NewReqExt *RequestExt + ResponseExt openrtb_ext.ExtBidResponse + MarketPlaceBidders map[string]struct{} + + AdapterThrottleMap map[string]struct{} + + AdUnitConfig *adunitconfig.AdUnitConfig + + Source, Origin string + + SendAllBids bool + WinningBids map[string]OwBid + DroppedBids map[string][]openrtb2.Bid + DefaultBids map[string]map[string][]openrtb2.Bid + SeatNonBids map[string][]openrtb_ext.NonBid // map of bidder to list of nonbids + + BidderResponseTimeMillis map[string]int + + Endpoint string + PubIDStr, ProfileIDStr string // TODO: remove this once we completely move away from header-bidding + MetricsEngine metrics.MetricsEngine + ReturnAllBidStatus bool // ReturnAllBidStatus stores the value of request.ext.prebid.returnallbidstatus + Sshb string //Sshb query param to identify that the request executed heder-bidding or not, sshb=1(executed HB(8001)), sshb=2(reverse proxy set from HB(8001->8000)), sshb=""(direct request(8000)). + + DCName string + CachePutMiss int // to be used in case of CTV JSON endpoint/amp/inapp-ott-video endpoint + CurrencyConversion func(from string, to string, value float64) (float64, error) + MatchedImpression map[string]int +} + +type OwBid struct { + ID string + NetEcpm float64 + BidDealTierSatisfied bool + Nbr *openrtb3.NonBidStatusCode +} + +func (r RequestCtx) GetVersionLevelKey(key string) string { + if len(r.PartnerConfigMap) == 0 || len(r.PartnerConfigMap[VersionLevelConfigID]) == 0 { + return "" + } + v := r.PartnerConfigMap[VersionLevelConfigID][key] + return v +} + +type ImpCtx struct { + ImpID string + TagID string + Div string + SlotName string + AdUnitName string + Secure int + BidFloor float64 + BidFloorCur string + IsRewardInventory *int8 + Banner bool + Video *openrtb2.Video + Native *openrtb2.Native + IncomingSlots []string + Type string // banner, video, native, etc + Bidders map[string]PartnerData + NonMapped map[string]struct{} + + NewExt json.RawMessage + BidCtx map[string]BidCtx + + BannerAdUnitCtx AdUnitCtx + VideoAdUnitCtx AdUnitCtx + + //temp + BidderError string + IsAdPodRequest bool +} + +type PartnerData struct { + PartnerID int + PrebidBidderCode string + MatchedSlot string + KGP string + KGPV string + IsRegex bool + Params json.RawMessage + VASTTagFlag bool + VASTTagFlags map[string]bool +} + +type BidCtx struct { + BidExt + + // EG gross net in USD for tracker and logger + EG float64 + // EN gross net in USD for tracker and logger + EN float64 +} + +type AdUnitCtx struct { + MatchedSlot string + IsRegex bool + MatchedRegex string + SelectedSlotAdUnitConfig *adunitconfig.AdConfig + AppliedSlotAdUnitConfig *adunitconfig.AdConfig + UsingDefaultConfig bool + AllowedConnectionTypes []int +} diff --git a/modules/pubmatic/openwrap/models/openwrap_test.go b/modules/pubmatic/openwrap/models/openwrap_test.go new file mode 100644 index 00000000000..5ae43cbe1f0 --- /dev/null +++ b/modules/pubmatic/openwrap/models/openwrap_test.go @@ -0,0 +1,45 @@ +package models + +import ( + "testing" +) + +func TestRequestCtx_GetVersionLevelKey(t *testing.T) { + type fields struct { + PartnerConfigMap map[int]map[string]string + } + type args struct { + key string + } + tests := []struct { + name string + fields fields + args args + want string + }{ + { + name: "get_version_level_platform_key", + fields: fields{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + "platform": "in-app", + }, + }, + }, + args: args{ + key: "platform", + }, + want: "in-app", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := RequestCtx{ + PartnerConfigMap: tt.fields.PartnerConfigMap, + } + if got := r.GetVersionLevelKey(tt.args.key); got != tt.want { + t.Errorf("RequestCtx.GetVersionLevelKey() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/models/ortb.go b/modules/pubmatic/openwrap/models/ortb.go new file mode 100644 index 00000000000..df1a7961ce0 --- /dev/null +++ b/modules/pubmatic/openwrap/models/ortb.go @@ -0,0 +1,72 @@ +package models + +// OpenRTB Constants +const ( + //constant for adformat + Banner = "banner" + Video = "video" + Native = "native" + + //constants for reading video keys from adunit Config + VideoMinDuration = "minduration" + VideoMaxDuration = "maxduration" + VideoSkip = "skip" + VideoSkipMin = "skipmin" + VideoSkipAfter = "skipafter" + VideoBattr = "battr" + VideoConnectionType = "connectiontype" + VideoMinBitRate = "minbitrate" + VideoMaxBitRate = "maxbitrate" + VideoMaxExtended = "maxextended" + VideoStartDelay = "startdelay" + VideoPlacement = "placement" + VideoLinearity = "linearity" + VideoMimes = "mimes" + VideoProtocol = "protocol" + VideoProtocols = "protocols" + VideoW = "w" + VideoH = "h" + VideoSequence = "sequence" + VideoBoxingAllowed = "boxingallowed" + VideoPlaybackMethod = "playbackmethod" + VidepPlaybackEnd = "playbackend" + VideoDelivery = "delivery" + VideoPos = "pos" + VideoAPI = "api" + VideoCompanionType = "companiontype" + VideoComapanionAd = "companionad" + + //banner obj + BannerFormat = "format" + BannerW = "w" + BannerH = "h" + BannerWMax = "wmax" + BannerHMax = "hmax" + BannerWMin = "wmin" + BannerHMin = "hmin" + BannerBType = "btype" + BannerBAttr = "battr" + BannerPos = "pos" + BannerMimes = "mimes" + BannerTopFrame = "topframe" + BannerExpdir = "expdir" + BannerAPI = "api" + BannerID = "id" + BannerVcm = "vcm" + + //format object + FormatW = "w" + FormatH = "h" + FormatWRatio = "wratio" + FormatHRatio = "hratio" + FormatWmin = "wmin" +) + +type ConsentType int + +const ( + Unknown ConsentType = iota + TCF_V1 + TCF_V2 + CCPA +) diff --git a/modules/pubmatic/openwrap/models/reponse.go b/modules/pubmatic/openwrap/models/reponse.go new file mode 100644 index 00000000000..98b3742d336 --- /dev/null +++ b/modules/pubmatic/openwrap/models/reponse.go @@ -0,0 +1,88 @@ +package models + +import ( + "encoding/json" + + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type BidExt struct { + openrtb_ext.ExtBid + + ErrorCode int `json:"errorCode,omitempty"` + ErrorMsg string `json:"errorMessage,omitempty"` + RefreshInterval int `json:"refreshInterval,omitempty"` + CreativeType string `json:"crtype,omitempty"` + // AdPod ExtBidPrebidAdPod `json:"adpod,omitempty"` + Summary []Summary `json:"summary,omitempty"` + SKAdnetwork json.RawMessage `json:"skadn,omitempty"` + Video *ExtBidVideo `json:"video,omitempty"` + Banner *ExtBidBanner `json:"banner,omitempty"` + DspId int `json:"dspid,omitempty"` + Winner int `json:"winner,omitempty"` + NetECPM float64 `json:"netecpm,omitempty"` + + OriginalBidCPM float64 `json:"origbidcpm,omitempty"` + OriginalBidCur string `json:"origbidcur,omitempty"` + OriginalBidCPMUSD float64 `json:"origbidcpmusd,omitempty"` + Nbr *openrtb3.NonBidStatusCode `json:"-"` // Reason for not bidding + Fsc int `json:"fsc,omitempty"` +} + +// ExtBidVideo defines the contract for bidresponse.seatbid.bid[i].ext.video +type ExtBidVideo struct { + MinDuration int64 `json:"minduration,omitempty"` // Minimum video ad duration in seconds. + MaxDuration int64 `json:"maxduration,omitempty"` // Maximum video ad duration in seconds. + Skip *int8 `json:"skip,omitempty"` // Indicates if the player will allow the video to be skipped,where 0 = no, 1 = yes. + SkipMin int64 `json:"skipmin,omitempty"` // Videos of total duration greater than this number of seconds can be skippable; only applicable if the ad is skippable. + SkipAfter int64 `json:"skipafter,omitempty"` // Number of seconds a video must play before skipping is enabled; only applicable if the ad is skippable. + BAttr []adcom1.CreativeAttribute `json:"battr,omitempty"` // Blocked creative attributes + PlaybackMethod []adcom1.PlaybackMethod `json:"playbackmethod,omitempty"` // Allowed playback methods + ClientConfig json.RawMessage `json:"clientconfig,omitempty"` +} + +// ExtBidBanner defines the contract for bidresponse.seatbid.bid[i].ext.banner +type ExtBidBanner struct { + ClientConfig json.RawMessage `json:"clientconfig,omitempty"` +} + +// ExtBidPrebidCache defines the contract for bidresponse.seatbid.bid[i].ext.prebid.cache +type ExtBidPrebidCache struct { + Key string `json:"key"` + Url string `json:"url"` +} + +// Prebid Response Ext with DspId +type OWExt struct { + openrtb_ext.ExtOWBid + DspId int `json:"dspid,omitempty"` + + OriginalBidCPM float64 `json:"origbidcpm,omitempty"` + OriginalBidCur string `json:"origbidcur,omitempty"` + OriginalBidCPMUSD float64 `json:"origbidcpmusd,omitempty"` +} + +// ExtBidderMessage defines an error object to be returned, consiting of a machine readable error code, and a human readable error message string. +type ExtBidderMessage struct { + Code int `json:"code"` + Message string `json:"message"` +} + +// BidType describes the allowed values for bidresponse.seatbid.bid[i].ext.prebid.type +type BidType string + +// Targeting map of string of strings +type Targeting map[string]string + +type Summary struct { + VastTagID string `json:"vastTagID,omitempty"` + Bidder string `json:"bidder,omitempty"` + Bid float64 `json:"bid,omitempty"` + ErrorCode int `json:"errorCode,omitempty"` + ErrorMessage string `json:"errorMessage,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + Regex string `json:"regex,omitempty"` +} diff --git a/modules/pubmatic/openwrap/models/request.go b/modules/pubmatic/openwrap/models/request.go new file mode 100644 index 00000000000..611a5a34f16 --- /dev/null +++ b/modules/pubmatic/openwrap/models/request.go @@ -0,0 +1,114 @@ +package models + +import ( + "encoding/json" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type ExtRegs struct { + // GDPR should be "1" if the caller believes the user is subject to GDPR laws, "0" if not, and undefined + // if it's unknown. For more info on this parameter, see: https://iabtechlab.com/wp-content/uploads/2018/02/OpenRTB_Advisory_GDPR_2018-02.pdf + Gdpr *int `json:"gdpr,omitempty"` + // USPrivacy should be a four character string, see: https://iabtechlab.com/wp-content/uploads/2019/11/OpenRTB-Extension-U.S.-Privacy-IAB-Tech-Lab.pdf + USPrivacy *string `json:"us_privacy,omitempty"` +} + +// ExtRequestAdPod holds AdPod specific extension parameters at request level +type ExtRequestAdPod struct { + AdPod + CrossPodAdvertiserExclusionPercent *int `json:"crosspodexcladv,omitempty"` //Percent Value - Across multiple impression there will be no ads from same advertiser. Note: These cross pod rule % values can not be more restrictive than per pod + CrossPodIABCategoryExclusionPercent *int `json:"crosspodexcliabcat,omitempty"` //Percent Value - Across multiple impression there will be no ads from same advertiser + IABCategoryExclusionWindow *int `json:"excliabcatwindow,omitempty"` //Duration in minute between pods where exclusive IAB rule needs to be applied + AdvertiserExclusionWindow *int `json:"excladvwindow,omitempty"` //Duration in minute between pods where exclusive advertiser rule needs to be applied +} + +// AdPod holds Video AdPod specific extension parameters at impression level +type AdPod struct { + MinAds *int `json:"minads,omitempty"` //Default 1 if not specified + MaxAds *int `json:"maxads,omitempty"` //Default 1 if not specified + MinDuration *int `json:"adminduration,omitempty"` // (adpod.adminduration * adpod.minads) should be greater than or equal to video.minduration + MaxDuration *int `json:"admaxduration,omitempty"` // (adpod.admaxduration * adpod.maxads) should be less than or equal to video.maxduration + video.maxextended + AdvertiserExclusionPercent *int `json:"excladv,omitempty"` // Percent value 0 means none of the ads can be from same advertiser 100 means can have all same advertisers + IABCategoryExclusionPercent *int `json:"excliabcat,omitempty"` // Percent value 0 means all ads should be of different IAB categories. +} + +// ImpExtension - Impression Extension +type ImpExtension struct { + Wrapper *ExtImpWrapper `json:"wrapper,omitempty"` + Reward *int8 `json:"reward,omitempty"` + + Bidder map[string]*BidderExtension `json:"bidder,omitempty"` + + SKAdnetwork json.RawMessage `json:"skadn,omitempty"` + Data openrtb_ext.ExtImpData `json:"data,omitempty"` + GpId string `json:"gpid,omitempty"` + Prebid openrtb_ext.ExtImpPrebid `json:"prebid,omitempty"` +} + +// BidderExtension - Bidder specific items +type BidderExtension struct { + KeyWords []KeyVal `json:"keywords,omitempty"` + DealTier *openrtb_ext.DealTier `json:"dealtier,omitempty"` +} + +// ExtImpWrapper - Impression wrapper Extension +type ExtImpWrapper struct { + Div string `json:"div,omitempty"` +} + +// ExtVideo structure to accept video specific more parameters like adpod +type ExtVideo struct { + Offset *int `json:"offset,omitempty"` // Minutes from start where this ad is intended to show + AdPod *AdPod `json:"adpod,omitempty"` +} + +// RequestExt Request Extension +type RequestExt struct { + openrtb_ext.ExtRequest + + // Move this to bidder params + Wrapper *RequestExtWrapper `json:"wrapper,omitempty"` + Bidder map[string]map[string]interface{} `json:"bidder,omitempty"` + AdPod *ExtRequestAdPod `json:"adpod,omitempty"` +} + +// pbopenrtb_ext alias for prebid server openrtb_ext +// type PriceFloorRules = openrtb_ext.PriceFloorRules + +// TransparencyRule contains transperancy rule for a single bidder +type TransparencyRule struct { + Include bool `json:"include,omitempty"` + Keys []string `json:"keys,omitempty"` +} + +// ExtTransparency holds bidder level content transparency rules +type ExtTransparency struct { + Content map[string]TransparencyRule `json:"content,omitempty"` +} + +// KeyVal structure to store bidder related custom key-values +type KeyVal struct { + Key string `json:"key,omitempty"` + Values []string `json:"value,omitempty"` +} + +// RequestExtWrapper holds wrapper specific extension parameters +type RequestExtWrapper struct { + ProfileId int `json:"profileid,omitempty"` + VersionId int `json:"versionid,omitempty"` + SSAuctionFlag int `json:"ssauction,omitempty"` + SumryDisableFlag int `json:"sumry_disable,omitempty"` + ClientConfigFlag int `json:"clientconfig,omitempty"` + LogInfoFlag int `json:"loginfo,omitempty"` + SupportDeals bool `json:"supportdeals,omitempty"` + IncludeBrandCategory int `json:"includebrandcategory,omitempty"` + ABTestConfig int `json:"abtest,omitempty"` + LoggerImpressionID string `json:"wiid,omitempty"` + SSAI string `json:"ssai,omitempty"` +} + +type BidderWrapper struct { + Flag bool + VASTagFlags map[string]bool +} diff --git a/modules/pubmatic/openwrap/models/source.go b/modules/pubmatic/openwrap/models/source.go new file mode 100644 index 00000000000..be0ea5154de --- /dev/null +++ b/modules/pubmatic/openwrap/models/source.go @@ -0,0 +1,9 @@ +package models + +import "github.com/prebid/prebid-server/v2/openrtb_ext" + +type ExtSource struct { + *openrtb_ext.ExtSource + OMIDPV string `json:"omidpv,omitempty"` + OMIDPN string `json:"omidpn,omitempty"` +} diff --git a/modules/pubmatic/openwrap/models/tracker.go b/modules/pubmatic/openwrap/models/tracker.go new file mode 100644 index 00000000000..fd4b2ac7150 --- /dev/null +++ b/modules/pubmatic/openwrap/models/tracker.go @@ -0,0 +1,79 @@ +package models + +// OWTracker vast video parameters to be injected +type OWTracker struct { + Tracker Tracker + TrackerURL string + ErrorURL string + Price float64 + PriceModel string + PriceCurrency string + BidType string `json:"-"` // video, banner, native + DspId int `json:"-"` // dsp id +} + +// Tracker tracker url creation parameters +type Tracker struct { + PubID int + PageURL string + Timestamp int64 + IID string + ProfileID string + VersionID string + SlotID string + Adunit string + PartnerInfo Partner + RewardedInventory int + SURL string // contains either req.site.domain or req.app.bundle value + Platform int + // SSAI identifies the name of the SSAI vendor + // Applicable only in case of incase of video/json endpoint. + SSAI string + AdPodSlot int + TestGroup int + Origin string + FloorSkippedFlag *int + FloorModelVersion string + FloorSource *int + FloorType int + LoggerData LoggerData // need this in logger to avoid duplicate computation + + ImpID string `json:"-"` + Secure int `json:"-"` +} + +// Partner partner information to be logged in tracker object +type Partner struct { + PartnerID string + BidderCode string + KGPV string + GrossECPM float64 + NetECPM float64 + BidID string + OrigBidID string + AdSize string + AdDuration int + Adformat string + ServerSide int + Advertiser string + FloorValue float64 + FloorRuleValue float64 + DealID string +} + +// LoggerData: this data to be needed in logger +type LoggerData struct { + KGPSV string + FloorProvider string + FloorFetchStatus *int +} + +// FloorsDetails contains floors info derived from responseExt.Prebid.Floors +type FloorsDetails struct { + FloorType int + FloorModelVersion string + FloorProvider string + Skipfloors *int + FloorFetchStatus *int + FloorSource *int +} diff --git a/modules/pubmatic/openwrap/models/tracking.go b/modules/pubmatic/openwrap/models/tracking.go new file mode 100644 index 00000000000..9018b81578f --- /dev/null +++ b/modules/pubmatic/openwrap/models/tracking.go @@ -0,0 +1,105 @@ +package models + +import "github.com/prebid/prebid-server/v2/openrtb_ext" + +// impression tracker url parameters +const ( + // constants for query parameter names for tracker call + TRKPubID = "pubid" + TRKPageURL = "purl" + TRKTimestamp = "tst" + TRKIID = "iid" + TRKProfileID = "pid" + TRKVersionID = "pdvid" + TRKIP = "ip" + TRKUserAgent = "ua" + TRKSlotID = "slot" + TRKAdunit = "au" + TRKRewardedInventory = "rwrd" + TRKPartnerID = "pn" + TRKBidderCode = "bc" + TRKKGPV = "kgpv" + TRKGrossECPM = "eg" + TRKNetECPM = "en" + TRKBidID = "bidid" + TRKOrigBidID = "origbidid" + TRKQMARK = "?" + TRKAmpersand = "&" + TRKSSAI = "ssai" + TRKPlatform = "plt" + TRKAdSize = "psz" + TRKTestGroup = "tgid" + TRKAdvertiser = "adv" + TRKPubDomain = "orig" + TRKServerSide = "ss" + TRKAdformat = "af" + TRKAdDuration = "dur" + TRKAdPodExist = "aps" + TRKFloorType = "ft" + TRKFloorModelVersion = "fmv" + TRKFloorSkippedFlag = "fskp" + TRKFloorSource = "fsrc" + TRKFloorValue = "fv" + TRKFloorRuleValue = "frv" + TRKServerLogger = "sl" + TRKDealID = "di" +) + +// video error tracker url parameters +const ( + ERROperIDValue = "8" + ERROperID = "operId" + ERROperIDParam = ERROperID + "=" + ERROperIDValue + ERRPubID = "p" + ERRProfileID = "pid" + ERRVersionID = "v" + ERRTimestamp = "ts" + ERRPartnerID = "pn" + ERRBidderCode = "bc" + ERRAdunit = "au" + ERRCreativeID = "crId" + ERRErrorCode = "ier" + ERRErrorCodeMacro = "[ERRORCODE]" + ERRErrorCodeParam = ERRErrorCode + "=" + ERRErrorCodeMacro + ERRSUrl = "sURL" // key represents either domain or bundle from request + ERRPlatform = "pfi" + ERRAdvertiser = "adv" + ERRSSAI = "ssai" +) + +// EventTrackingMacros Video Event Tracker's custom macros +type EventTrackingMacros string + +const ( + MacroProfileID EventTrackingMacros = "[PROFILE_ID]" // Pass Profile ID using this macro + MacroProfileVersionID EventTrackingMacros = "[PROFILE_VERSION]" // Pass Profile's version ID using this macro + MacroUnixTimeStamp EventTrackingMacros = "[UNIX_TIMESTAMP]" // Pass Current Unix Time when Event Tracking URL is generated using this macro + MacroPlatform EventTrackingMacros = "[PLATFORM]" // Pass PubMatic's Platform using this macro + MacroWrapperImpressionID EventTrackingMacros = "[WRAPPER_IMPRESSION_ID]" // Pass Wrapper Impression ID using this macro + MacroSSAI EventTrackingMacros = "[SSAI]" // Pass SSAI vendor name using this macro +) + +// DspId for Pixel Based Open Measurement +const ( + DspId_DV360 = 80 +) + +var FloorSourceMap = map[string]int{ + openrtb_ext.NoDataLocation: 0, + openrtb_ext.RequestLocation: 1, + openrtb_ext.FetchLocation: 2, +} + +// FetchStatusMap maps floor fetch status with integer codes +var FetchStatusMap = map[string]int{ + openrtb_ext.FetchNone: 0, + openrtb_ext.FetchSuccess: 1, + openrtb_ext.FetchError: 2, + openrtb_ext.FetchInprogress: 3, + openrtb_ext.FetchTimeout: 4, +} + +const ( + NotSet = -1 + DealIDAbsent = "-1" +) diff --git a/modules/pubmatic/openwrap/models/utils.go b/modules/pubmatic/openwrap/models/utils.go new file mode 100644 index 00000000000..2e825c230d3 --- /dev/null +++ b/modules/pubmatic/openwrap/models/utils.go @@ -0,0 +1,415 @@ +package models + +import ( + "encoding/json" + "fmt" + "math" + "net" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + + "github.com/buger/jsonparser" + "github.com/pkg/errors" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/ptrutil" +) + +var videoRegex *regexp.Regexp + +func init() { + videoRegex, _ = regexp.Compile(" 0 { + return string(bidExt.Prebid.Type) + } + if bid.AdM == "" { + return "" + } + if videoRegex.MatchString(bid.AdM) { + return Video + } + if impCtx.Native != nil { + var admJSON map[string]interface{} + err := json.Unmarshal([]byte(strings.Replace(bid.AdM, "/\\/g", "", -1)), &admJSON) + if err == nil && admJSON != nil && admJSON["native"] != nil { + return Native + } + } + return Banner +} + +func IsDefaultBid(bid *openrtb2.Bid) bool { + return bid.Price == 0 && bid.DealID == "" && bid.W == 0 && bid.H == 0 +} + +// GetAdFormat returns adformat of the bid. +// for default bid it refers to impression object +// for non-default bids it uses creative(adm) of the bid +func GetAdFormat(bid *openrtb2.Bid, bidExt *BidExt, impCtx *ImpCtx) string { + if bid == nil || impCtx == nil { + return "" + } + if IsDefaultBid(bid) { + if impCtx.Banner { + return Banner + } + if impCtx.Video != nil { + return Video + } + if impCtx.Native != nil { + return Native + } + return "" + } + if bidExt == nil { + return "" + } + return GetCreativeType(bid, bidExt, impCtx) +} + +func GetRevenueShare(partnerConfig map[string]string) float64 { + var revShare float64 + + if val, ok := partnerConfig[REVSHARE]; ok { + revShare, _ = strconv.ParseFloat(val, 64) + } + return revShare +} + +func GetNetEcpm(price float64, revShare float64) float64 { + if revShare == 0 { + return toFixed(price, BID_PRECISION) + } + price = price * (1 - revShare/100) + 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 +} + +func round(num float64) int { + return int(num + math.Copysign(0.5, num)) +} + +func ExtractDomain(rawURL string) (string, error) { + if !strings.HasPrefix(rawURL, "http") { + rawURL = "http://" + rawURL + } + + u, err := url.Parse(rawURL) + if err != nil { + return "", err + } + + return u.Host, nil +} + +// hybrid/web request would have bidder params prepopulated. +// TODO: refer request.ext.prebid.channel.name = pbjs instead? +func IsHybrid(body []byte) bool { + _, _, _, err := jsonparser.Get(body, "imp", "[0]", "ext", "prebid", "bidder", "pubmatic") + return err == nil +} + +// GetVersionLevelPropertyFromPartnerConfig returns a Version level property from the partner config map +func GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap map[int]map[string]string, propertyName string) string { + if versionLevelConfig, ok := partnerConfigMap[VersionLevelConfigID]; ok && versionLevelConfig != nil { + return versionLevelConfig[propertyName] + } + return "" +} + +const ( + //The following are the headerds related to IP address + XForwarded = "X-FORWARDED-FOR" + SourceIP = "SOURCE_IP" + ClusterClientIP = "X_CLUSTER_CLIENT_IP" + RemoteAddr = "REMOTE_ADDR" + RlnClientIP = "RLNCLIENTIPADDR" +) + +func GetIP(in *http.Request) string { + //The IP address priority is as follows: + //0. HTTP_RLNCLIENTIPADDR //For API + //1. HTTP_X_FORWARDED_IP + //2. HTTP_X_CLUSTER_CLIENT_IP + //3. HTTP_SOURCE_IP + //4. REMOTE_ADDR + ip := in.Header.Get(RlnClientIP) + if ip == "" { + ip = in.Header.Get(SourceIP) + if ip == "" { + ip = in.Header.Get(ClusterClientIP) + if ip == "" { + ip = in.Header.Get(XForwarded) + if ip == "" { + //RemoteAddr parses the header REMOTE_ADDR + ip = in.Header.Get(RemoteAddr) + if ip == "" { + ip, _, _ = net.SplitHostPort(in.RemoteAddr) + } + } + } + } + } + ips := strings.Split(ip, ",") + if len(ips) != 0 { + return ips[0] + } + + return "" +} + +func Atof(value string, decimalplaces int) (float64, error) { + + floatValue, err := strconv.ParseFloat(value, 64) + if err != nil { + return 0, err + } + + value = fmt.Sprintf("%."+strconv.Itoa(decimalplaces)+"f", floatValue) + floatValue, err = strconv.ParseFloat(value, 64) + if err != nil { + return 0, err + } + + return floatValue, nil +} + +// IsPubmaticCorePartner returns true when the partner is pubmatic or internally an alias of pubmatic +func IsPubmaticCorePartner(partnerName string) bool { + if partnerName == string(openrtb_ext.BidderPubmatic) || partnerName == BidderPubMaticSecondaryAlias { + return true + } + return false +} + +// wraps error with error msg +func ErrorWrap(cErr, nErr error) error { + if cErr == nil { + return nErr + } + + return errors.Wrap(cErr, nErr.Error()) +} + +func GetSizeForPlatform(width, height int64, platform string) string { + s := fmt.Sprintf("%dx%d", width, height) + if platform == PLATFORM_VIDEO { + s = s + VideoSizeSuffix + } + return s +} + +func GetKGPSV(bid openrtb2.Bid, bidderMeta PartnerData, adformat string, tagId string, div string, source string) (string, string) { + kgpv := bidderMeta.KGPV + kgpsv := bidderMeta.MatchedSlot + isRegex := bidderMeta.IsRegex + // 1. nobid + if IsDefaultBid(&bid) { + //NOTE: kgpsv = bidderMeta.MatchedSlot above. Use the same + if !isRegex && kgpv != "" { // unmapped pubmatic's slot + kgpsv = kgpv + } else if !isRegex { + kgpv = kgpsv + } + } else if !isRegex { + if kgpv != "" { // unmapped pubmatic's slot + kgpsv = kgpv + } else if adformat == Video { // Check when adformat is video, bid.W and bid.H has to be zero with Price !=0. Ex: UOE-9222(0x0 default kgpv and kgpsv for video bid) + // 2. valid video bid + // kgpv has regex, do not generate slotName again + // kgpsv could be unmapped or mapped slot, generate slotName with bid.W = bid.H = 0 + kgpsv = GenerateSlotName(0, 0, bidderMeta.KGP, tagId, div, source) + kgpv = kgpsv // original /43743431/DMDemo1234@300x250 but new could be /43743431/DMDemo1234@0x0 + } else if bid.H != 0 && bid.W != 0 { // Check when bid.H and bid.W will be zero with Price !=0. Ex: MobileInApp-MultiFormat-OnlyBannerMapping_Criteo_Partner_Validaton + // 3. valid bid + // kgpv has regex, do not generate slotName again + // kgpsv could be unmapped or mapped slot, generate slotName again based on bid.H and bid.W + kgpsv = GenerateSlotName(bid.H, bid.W, bidderMeta.KGP, tagId, div, source) + kgpv = kgpsv + } + } + if kgpv == "" { + kgpv = kgpsv + } + return kgpv, kgpsv +} + +// Harcode would be the optimal. We could make it configurable like _AU_@_W_x_H_:%s@%dx%d entries in pbs.yaml +// mysql> SELECT DISTINCT key_gen_pattern FROM wrapper_mapping_template; +// +----------------------+ +// | key_gen_pattern | +// +----------------------+ +// | _AU_@_W_x_H_ | +// | _DIV_@_W_x_H_ | +// | _W_x_H_@_W_x_H_ | +// | _DIV_ | +// | _AU_@_DIV_@_W_x_H_ | +// | _AU_@_SRC_@_VASTTAG_ | +// +----------------------+ +// 6 rows in set (0.21 sec) +func GenerateSlotName(h, w int64, kgp, tagid, div, src string) string { + // func (H, W, Div), no need to validate, will always be non-nil + switch kgp { + case "_AU_": // adunitconfig + return tagid + case "_DIV_": + return div + case "_AU_@_W_x_H_": + return fmt.Sprintf("%s@%dx%d", tagid, w, h) + case "_DIV_@_W_x_H_": + return fmt.Sprintf("%s@%dx%d", div, w, h) + case "_W_x_H_@_W_x_H_": + return fmt.Sprintf("%dx%d@%dx%d", w, h, w, h) + case "_AU_@_DIV_@_W_x_H_": + return fmt.Sprintf("%s@%s@%dx%d", tagid, div, w, h) + case "_AU_@_SRC_@_VASTTAG_": + return fmt.Sprintf("%s@%s@_VASTTAG_", tagid, src) //TODO check where/how _VASTTAG_ is updated + default: + // TODO: check if we need to fallback to old generic flow (below) + // Add this cases in a map and read it from yaml file + } + return "" +} + +func RoundToTwoDigit(value float64) float64 { + output := math.Pow(10, float64(2)) + return float64(math.Round(value*output)) / output +} + +// GetBidLevelFloorsDetails return floorvalue and floorrulevalue +func GetBidLevelFloorsDetails(bidExt BidExt, impCtx ImpCtx, + currencyConversion func(from, to string, value float64) (float64, error)) (fv, frv float64) { + var floorCurrency string + frv = NotSet + + if bidExt.Prebid != nil && bidExt.Prebid.Floors != nil { + floorCurrency = bidExt.Prebid.Floors.FloorCurrency + fv = RoundToTwoDigit(bidExt.Prebid.Floors.FloorValue) + frv = fv + if bidExt.Prebid.Floors.FloorRuleValue > 0 { + frv = RoundToTwoDigit(bidExt.Prebid.Floors.FloorRuleValue) + } + } + + // if floor values are not set from bid.ext then fall back to imp.bidfloor + if frv == NotSet && impCtx.BidFloor != 0 { + fv = RoundToTwoDigit(impCtx.BidFloor) + frv = fv + floorCurrency = impCtx.BidFloorCur + } + + // convert the floor values in USD currency + if floorCurrency != "" && floorCurrency != USD { + value, _ := currencyConversion(floorCurrency, USD, fv) + fv = RoundToTwoDigit(value) + value, _ = currencyConversion(floorCurrency, USD, frv) + frv = RoundToTwoDigit(value) + } + + if frv == NotSet { + frv = 0 // set it back to 0 + } + + return +} + +// GetFloorsDetails returns floors details from response.ext.prebid +func GetFloorsDetails(responseExt openrtb_ext.ExtBidResponse) (floorDetails FloorsDetails) { + if responseExt.Prebid != nil && responseExt.Prebid.Floors != nil { + floors := responseExt.Prebid.Floors + if floors.Skipped != nil { + floorDetails.Skipfloors = ptrutil.ToPtr(0) + if *floors.Skipped { + floorDetails.Skipfloors = ptrutil.ToPtr(1) + } + } + if floors.Data != nil && len(floors.Data.ModelGroups) > 0 { + floorDetails.FloorModelVersion = floors.Data.ModelGroups[0].ModelVersion + } + if len(floors.PriceFloorLocation) > 0 { + if source, ok := FloorSourceMap[floors.PriceFloorLocation]; ok { + floorDetails.FloorSource = &source + } + } + if status, ok := FetchStatusMap[floors.FetchStatus]; ok { + floorDetails.FloorFetchStatus = &status + } + floorDetails.FloorProvider = floors.FloorProvider + if floors.Data != nil && len(floors.Data.FloorProvider) > 0 { + floorDetails.FloorProvider = floors.Data.FloorProvider + } + if floors.Enforcement != nil && floors.Enforcement.EnforcePBS != nil && *floors.Enforcement.EnforcePBS { + floorDetails.FloorType = HardFloor + } + } + return floorDetails +} diff --git a/modules/pubmatic/openwrap/models/utils_legacy.go b/modules/pubmatic/openwrap/models/utils_legacy.go new file mode 100644 index 00000000000..cbb866d5116 --- /dev/null +++ b/modules/pubmatic/openwrap/models/utils_legacy.go @@ -0,0 +1,17 @@ +package models + +import ( + "encoding/json" + "fmt" +) + +func GetRequestExt(ext []byte) (*RequestExt, error) { + extRequest := &RequestExt{} + + err := json.Unmarshal(ext, extRequest) + if err != nil { + return nil, fmt.Errorf("failed to decode request.ext : %v", err) + } + + return extRequest, nil +} diff --git a/modules/pubmatic/openwrap/models/utils_legacy_test.go b/modules/pubmatic/openwrap/models/utils_legacy_test.go new file mode 100644 index 00000000000..393f19b133d --- /dev/null +++ b/modules/pubmatic/openwrap/models/utils_legacy_test.go @@ -0,0 +1,82 @@ +package models + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestGetRequestExt(t *testing.T) { + type args struct { + ext []byte + } + tests := []struct { + name string + args args + want *RequestExt + wantErr bool + }{ + { + name: "empty_request_ext", + args: args{ + ext: []byte(`{}`), + }, + want: &RequestExt{}, + wantErr: false, + }, + { + name: "successfully_Unmarshaled_request_ext", + args: args{ + ext: []byte(`{"prebid":{},"wrapper":{"ssauction":0,"profileid":2087,"sumry_disable":0,"clientconfig":1,"versionid":1}}`), + }, + want: &RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{}, + }, + Wrapper: &RequestExtWrapper{ + SSAuctionFlag: 0, + ProfileId: 2087, + SumryDisableFlag: 0, + ClientConfigFlag: 1, + VersionId: 1, + }, + }, + wantErr: false, + }, + { + name: "failed_to_Unmarshaled_request_ext", + args: args{ + ext: []byte(`{"prebid":{},"wrapper":{"ssauction":0,"profileid":"2087","sumry_disable":0,"clientconfig":1,"versionid":1}}`), + }, + want: nil, + wantErr: true, + }, + { + name: "Invalid_JSON_request_ext", + args: args{ + ext: []byte(`Invalid json`), + }, + want: nil, + wantErr: true, + }, + { + name: "unexpected_end_of_JSON_input", + args: args{ + ext: nil, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetRequestExt(tt.args.ext) + if (err != nil) != tt.wantErr { + t.Errorf("GetRequestExt() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/models/utils_test.go b/modules/pubmatic/openwrap/models/utils_test.go new file mode 100644 index 00000000000..a02e0ad6e39 --- /dev/null +++ b/modules/pubmatic/openwrap/models/utils_test.go @@ -0,0 +1,1333 @@ +package models + +import ( + "fmt" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestErrorWrap(t *testing.T) { + type args struct { + cErr error + nErr error + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "current error as nil", + args: args{ + cErr: nil, + nErr: fmt.Errorf("error found for %d", 1234), + }, + wantErr: true, + }, + { + name: "wrap error", + args: args{ + cErr: fmt.Errorf("current error found for %d", 1234), + nErr: fmt.Errorf("new error found for %d", 1234), + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := ErrorWrap(tt.args.cErr, tt.args.nErr); (err != nil) != tt.wantErr { + t.Errorf("ErrorWrap() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestGetCreativeType(t *testing.T) { + type args struct { + bid *openrtb2.Bid + bidExt *BidExt + impCtx *ImpCtx + } + tests := []struct { + name string + args args + want string + }{ + { + name: "bid.ext.prebid.type absent", + args: args{ + bid: &openrtb2.Bid{}, + bidExt: &BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{}, + }, + }, + impCtx: &ImpCtx{}, + }, + want: "", + }, + { + name: "bid.ext.prebid.type empty", + args: args{ + bid: &openrtb2.Bid{}, + bidExt: &BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Type: "", + }, + }, + }, + impCtx: &ImpCtx{}, + }, + want: "", + }, + { + name: "bid.ext.prebid.type is banner", + args: args{ + bid: &openrtb2.Bid{}, + bidExt: &BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Type: Banner, + }, + }, + }, + impCtx: &ImpCtx{}, + }, + want: Banner, + }, + { + name: "bid.ext.prebid.type is video", + args: args{ + bid: &openrtb2.Bid{}, + bidExt: &BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Type: Video, + }, + }, + }, + impCtx: &ImpCtx{}, + }, + want: Video, + }, + { + name: "Banner Adm has json assets keyword", + args: args{ + bid: &openrtb2.Bid{ + AdM: `u003cscript src='mraid.js'>`, + }, + bidExt: &BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{}, + }, + }, + impCtx: &ImpCtx{}, + }, + want: Banner, + }, + { + name: "Empty Bid Adm", + args: args{ + bid: &openrtb2.Bid{ + AdM: "", + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: "", + }, + { + name: "VAST Ad", + args: args{ + bid: &openrtb2.Bid{ + AdM: "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1http://172.16.4.213/AdServer/AdDisplayTrackerServlethttps://dsptracker.com/{PSPM}http://172.16.4.213/trackhttps://Errortrack.com00:00:04http://172.16.4.213/trackhttps://www.pubmatic.comhttps://stagingnyc.pubmatic.com:8443/video/Shashank/mediaFileHost/media/mp4-sample-1.mp4]https://stagingnyc.pubmatic.com:8443/video/Shashank/mediaFileHost/media/mp4-sample-2.mp4]", + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: Video, + }, + { + name: "VAST Ad xml", + args: args{ + bid: &openrtb2.Bid{ + AdM: "adnxs", + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: Video, + }, + { + name: "Banner Ad", + args: args{ + bid: &openrtb2.Bid{ + AdM: "
", + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: Banner, + }, + { + name: "Native Adm with `native` Object", + args: args{ + bid: &openrtb2.Bid{ + AdM: `{"native":{"ver":1.2,"link":{"url":"https://dummyimage.com/1x1/000000/fff.jpg&text=420x420+Creative.jpg","clicktrackers":["http://image3.pubmatic.com/AdServer/layer?a={PUBMATIC_SECOND_PRICE}&ucrid=9335447642416814892&t=FNOZW09VkdSTVM0eU5BPT09JmlkPTAmY2lkPTIyNzcyJnhwcj0xLjAwMDAwMCZmcD00JnBwPTIuMzcxMiZ0cD0yJnBlPTAuMDA9=","http://image3.pubmatic.com/AdServer/layer?a={PUBMATIC_SECOND_PRICE}&ucrid=9335447642416814892&t=FNOZW09VkdSTVM0eU5BPT09JmlkPTAmY2lkPTIyNzcyJnhwcj0xLjAwMDAwMCZmcD00JnBwPTIuMzcxMiZ0cD0yJnBlPTAuMDA9="]},"eventtrackers":[{"event":1,"method":1,"url":"http://image3.pubmatic.com/AdServer/layer?a={PUBMATIC_SECOND_PRICE}&ucrid=9335447642416814892&t=FNOZW09VkdSTVM0eU5BPT09JmlkPTAmY2lkPTIyNzcyJnhwcj0xLjAwMDAwMCZmcD00JnBwPTIuMzcxMiZ0cD0yJnBlPTAuMDA9="}]}}`, + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{ + Native: &openrtb2.Native{}, + }, + }, + want: Native, + }, + { + name: "Native Adm with `native` and `assets` Object", + args: args{ + bid: &openrtb2.Bid{ + AdM: "{\"native\":{\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"Lexus - Luxury vehicles company\"}},{\"id\":2,\"img\":{\"h\":150,\"url\":\"https://stagingnyc.pubmatic.com:8443//sdk/lexus_logo.png\",\"w\":150},\"required\":0},{\"id\":3,\"img\":{\"h\":428,\"url\":\"https://stagingnyc.pubmatic.com:8443//sdk/28f48244cafa0363b03899f267453fe7%20copy.png\",\"w\":214},\"required\":0},{\"data\":{\"value\":\"Goto PubMatic\"},\"id\":4,\"required\":0},{\"data\":{\"value\":\"Lexus - Luxury vehicles company\"},\"id\":5,\"required\":0},{\"data\":{\"value\":\"4\"},\"id\":6,\"required\":0}],\"imptrackers\":[\"http://phtrack.pubmatic.com/?ts=1496043362&r=84137f17-eefd-4f06-8380-09138dc616e6&i=c35b1240-a0b3-4708-afca-54be95283c61&a=130917&t=9756&au=10002949&p=&c=10014299&o=10002476&wl=10009731&ty=1\"],\"link\":{\"clicktrackers\":[\"http://ct.pubmatic.com/track?ts=1496043362&r=84137f17-eefd-4f06-8380-09138dc616e6&i=c35b1240-a0b3-4708-afca-54be95283c61&a=130917&t=9756&au=10002949&p=&c=10014299&o=10002476&wl=10009731&ty=3&url=\"],\"url\":\"http://www.lexus.com/\"},\"ver\":1}}", + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{ + Native: &openrtb2.Native{}, + }, + }, + want: Native, + }, + { + name: "Native Adm with `native` Object but Native is missing in impCtx", + args: args{ + bid: &openrtb2.Bid{ + AdM: `{"native":{"ver":1.2,"link":{"url":"https://dummyimage.com/1x1/000000/fff.jpg&text=420x420+Creative.jpg","clicktrackers":["http://image3.pubmatic.com/AdServer/layer?a={PUBMATIC_SECOND_PRICE}&ucrid=9335447642416814892&t=FNOZW09VkdSTVM0eU5BPT09JmlkPTAmY2lkPTIyNzcyJnhwcj0xLjAwMDAwMCZmcD00JnBwPTIuMzcxMiZ0cD0yJnBlPTAuMDA9=","http://image3.pubmatic.com/AdServer/layer?a={PUBMATIC_SECOND_PRICE}&ucrid=9335447642416814892&t=FNOZW09VkdSTVM0eU5BPT09JmlkPTAmY2lkPTIyNzcyJnhwcj0xLjAwMDAwMCZmcD00JnBwPTIuMzcxMiZ0cD0yJnBlPTAuMDA9="]},"eventtrackers":[{"event":1,"method":1,"url":"http://image3.pubmatic.com/AdServer/layer?a={PUBMATIC_SECOND_PRICE}&ucrid=9335447642416814892&t=FNOZW09VkdSTVM0eU5BPT09JmlkPTAmY2lkPTIyNzcyJnhwcj0xLjAwMDAwMCZmcD00JnBwPTIuMzcxMiZ0cD0yJnBlPTAuMDA9="}]}}`, + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: Banner, + }, + { + name: "Video Adm \t", + args: args{ + bid: &openrtb2.Bid{ + AdM: "", + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: Video, + }, + { + name: "Video Adm \r", + args: args{ + bid: &openrtb2.Bid{ + AdM: "", + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: Video, + }, + { + name: "Video AdM \n", + args: args{ + bid: &openrtb2.Bid{ + AdM: "", + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: Video, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + creativeType := GetCreativeType(tt.args.bid, tt.args.bidExt, tt.args.impCtx) + assert.Equal(t, tt.want, creativeType, tt.name) + }) + } +} + +func TestGetAdFormat(t *testing.T) { + type args struct { + bid *openrtb2.Bid + bidExt *BidExt + impCtx *ImpCtx + } + tests := []struct { + name string + args args + want string + }{ + { + name: "no bid object", + args: args{ + bid: nil, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: "", + }, + { + name: "no bidExt object", + args: args{ + bid: nil, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: "", + }, + { + name: "no impctx object", + args: args{ + bid: nil, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: "", + }, + { + name: "Default bid and banner present", + args: args{ + bid: &openrtb2.Bid{ + DealID: "", + Price: 0, + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{ + Banner: true, + }, + }, + want: Banner, + }, + { + name: "Default bid and video present", + args: args{ + bid: &openrtb2.Bid{ + DealID: "", + Price: 0, + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{ + Video: &openrtb2.Video{}, + }, + }, + want: Video, + }, + { + name: "Default bid and native present", + args: args{ + bid: &openrtb2.Bid{ + DealID: "", + Price: 0, + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{ + Native: &openrtb2.Native{}, + }, + }, + want: Native, + }, + { + name: "Default bid and banner and video and native present", + args: args{ + bid: &openrtb2.Bid{ + DealID: "", + Price: 0, + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{ + Banner: true, + Native: &openrtb2.Native{}, + Video: &openrtb2.Video{}, + }, + }, + want: Banner, + }, + { + name: "Default bid and none of banner/video/native present", + args: args{ + bid: &openrtb2.Bid{ + DealID: "", + Price: 0, + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: "", + }, + { + name: "Empty Bid Adm", + args: args{ + bid: &openrtb2.Bid{ + Price: 10, + AdM: "", + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: "", + }, + { + name: "VAST Ad", + args: args{ + bid: &openrtb2.Bid{ + DealID: "dl", + AdM: "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1http://172.16.4.213/AdServer/AdDisplayTrackerServlethttps://dsptracker.com/{PSPM}http://172.16.4.213/trackhttps://Errortrack.com00:00:04http://172.16.4.213/trackhttps://www.pubmatic.comhttps://stagingnyc.pubmatic.com:8443/video/Shashank/mediaFileHost/media/mp4-sample-1.mp4]https://stagingnyc.pubmatic.com:8443/video/Shashank/mediaFileHost/media/mp4-sample-2.mp4]", + }, + bidExt: &BidExt{}, + impCtx: &ImpCtx{}, + }, + want: Video, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + creativeType := GetAdFormat(tt.args.bid, tt.args.bidExt, tt.args.impCtx) + assert.Equal(t, tt.want, creativeType, tt.name) + }) + } +} + +func TestGetSizeForPlatform(t *testing.T) { + type args struct { + width, height int64 + platform string + } + tests := []struct { + name string + args args + size string + }{ + { + name: "in-app platform", + args: args{ + width: 100, + height: 10, + platform: PLATFORM_APP, + }, + size: "100x10", + }, + { + name: "video platform", + args: args{ + width: 100, + height: 10, + platform: PLATFORM_VIDEO, + }, + size: "100x10v", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + size := GetSizeForPlatform(tt.args.width, tt.args.height, tt.args.platform) + assert.Equal(t, tt.size, size, tt.name) + }) + } +} + +func TestGenerateSlotName(t *testing.T) { + type args struct { + h int64 + w int64 + kgp string + tagid string + div string + src string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "_AU_", + args: args{ + h: 100, + w: 200, + kgp: "_AU_", + tagid: "/15671365/Test_Adunit", + div: "Div1", + src: "test.com", + }, + want: "/15671365/Test_Adunit", + }, + { + name: "_DIV_", + args: args{ + h: 100, + w: 200, + kgp: "_DIV_", + tagid: "/15671365/Test_Adunit", + div: "Div1", + src: "test.com", + }, + want: "Div1", + }, + { + name: "_AU_", + args: args{ + h: 100, + w: 200, + kgp: "_AU_", + tagid: "/15671365/Test_Adunit", + div: "Div1", + src: "test.com", + }, + want: "/15671365/Test_Adunit", + }, + { + name: "_AU_@_W_x_H_", + args: args{ + h: 100, + w: 200, + kgp: "_AU_@_W_x_H_", + tagid: "/15671365/Test_Adunit", + div: "Div1", + src: "test.com", + }, + want: "/15671365/Test_Adunit@200x100", + }, + { + name: "_DIV_@_W_x_H_", + args: args{ + h: 100, + w: 200, + kgp: "_DIV_@_W_x_H_", + tagid: "/15671365/Test_Adunit", + div: "Div1", + src: "test.com", + }, + want: "Div1@200x100", + }, + { + name: "_W_x_H_@_W_x_H_", + args: args{ + h: 100, + w: 200, + kgp: "_W_x_H_@_W_x_H_", + tagid: "/15671365/Test_Adunit", + div: "Div1", + src: "test.com", + }, + want: "200x100@200x100", + }, + { + name: "_AU_@_DIV_@_W_x_H_", + args: args{ + h: 100, + w: 200, + kgp: "_AU_@_DIV_@_W_x_H_", + tagid: "/15671365/Test_Adunit", + div: "Div1", + src: "test.com", + }, + want: "/15671365/Test_Adunit@Div1@200x100", + }, + { + name: "_AU_@_SRC_@_VASTTAG_", + args: args{ + h: 100, + w: 200, + kgp: "_AU_@_SRC_@_VASTTAG_", + tagid: "/15671365/Test_Adunit", + div: "Div1", + src: "test.com", + }, + want: "/15671365/Test_Adunit@test.com@_VASTTAG_", + }, + { + name: "empty_kgp", + args: args{ + h: 100, + w: 200, + kgp: "", + tagid: "/15671365/Test_Adunit", + div: "Div1", + src: "test.com", + }, + want: "", + }, + { + name: "random_kgp", + args: args{ + h: 100, + w: 200, + kgp: "fjkdfhk", + tagid: "/15671365/Test_Adunit", + div: "Div1", + src: "test.com", + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GenerateSlotName(tt.args.h, tt.args.w, tt.args.kgp, tt.args.tagid, tt.args.div, tt.args.src) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetRevenueShare(t *testing.T) { + tests := []struct { + name string + partnerConfig map[string]string + revshare float64 + }{ + { + name: "Empty partnerConfig", + partnerConfig: make(map[string]string), + revshare: 0, + }, + { + name: "partnerConfig without rev_share", + partnerConfig: map[string]string{ + "anykey": "anyval", + }, + revshare: 0, + }, + { + name: "partnerConfig with invalid rev_share", + partnerConfig: map[string]string{ + REVSHARE: "invalid", + }, + revshare: 0, + }, + { + name: "partnerConfig with valid rev_share", + partnerConfig: map[string]string{ + REVSHARE: "10", + }, + revshare: 10, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + revshare := GetRevenueShare(tt.partnerConfig) + assert.Equal(t, tt.revshare, revshare, tt.name) + }) + } +} + +func TestGetNetEcpm(t *testing.T) { + type args struct { + price, revShare float64 + } + tests := []struct { + name string + args args + netecpm float64 + }{ + { + name: "revshare is 0", + args: args{ + revShare: 0, + price: 10, + }, + netecpm: 10, + }, + { + name: "revshare is int", + args: args{ + revShare: 2, + price: 2, + }, + netecpm: 1.96, + }, + { + name: "revshare is float", + args: args{ + revShare: 3.338, + price: 100, + }, + netecpm: 96.66, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + netecpm := GetNetEcpm(tt.args.price, tt.args.revShare) + assert.Equal(t, tt.netecpm, netecpm, tt.name) + }) + } +} + +func TestGetGrossEcpm(t *testing.T) { + + tests := []struct { + name string + price float64 + grossecpm float64 + }{ + { + name: "grossecpm ceiling", + price: 18.998, + grossecpm: 19, + }, + { + name: "grossecpm floor", + price: 18.901, + grossecpm: 18.90, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + grossecpm := GetGrossEcpm(tt.price) + assert.Equal(t, tt.grossecpm, grossecpm, tt.name) + }) + } +} + +func TestExtractDomain(t *testing.T) { + type want struct { + domain string + err bool + } + tests := []struct { + name string + url string + want want + }{ + { + name: "url without http prefix", + url: "google.com", + want: want{ + domain: "google.com", + }, + }, + { + name: "url with http prefix", + url: "http://google.com", + want: want{ + domain: "google.com", + }, + }, + { + name: "url with https prefix", + url: "https://google.com", + want: want{ + domain: "google.com", + }, + }, + { + name: "invalid", + url: "https://google:com?a=1;b=2", + want: want{ + domain: "", + err: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + domain, err := ExtractDomain(tt.url) + assert.Equal(t, tt.want.domain, domain, tt.name) + assert.Equal(t, tt.want.err, err != nil, tt.name) + }) + } +} + +func TestGetBidLevelFloorsDetails(t *testing.T) { + type args struct { + bidExt BidExt + impCtx ImpCtx + currencyConversion func(from, to string, value float64) (float64, error) + } + type want struct { + fv, frv float64 + } + tests := []struct { + name string + args args + want want + }{ + { + name: "set_floor_values_from_bidExt", + args: args{ + bidExt: BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Floors: &openrtb_ext.ExtBidPrebidFloors{ + FloorRuleValue: 10, + FloorValue: 5, + }, + }, + }, + }, + impCtx: ImpCtx{ + BidFloor: 2.2, + BidFloorCur: "EUR", + }, + }, + want: want{ + fv: 5, + frv: 10, + }, + }, + { + name: "frv_absent_in_bidExt", + args: args{ + bidExt: BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Floors: &openrtb_ext.ExtBidPrebidFloors{ + FloorValue: 5, + }, + }, + }, + }, + impCtx: ImpCtx{ + BidFloor: 2.2, + BidFloorCur: "EUR", + }, + }, + want: want{ + fv: 5, + frv: 5, + }, + }, + { + name: "fv_is_0_in_bidExt", + args: args{ + bidExt: BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Floors: &openrtb_ext.ExtBidPrebidFloors{ + FloorValue: 0, + }, + }, + }, + }, + impCtx: ImpCtx{ + BidFloor: 2.2, + BidFloorCur: "EUR", + }, + }, + want: want{ + fv: 0, + frv: 0, + }, + }, + { + name: "currency_conversion_for_floor_values_in_bidExt", + args: args{ + bidExt: BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Floors: &openrtb_ext.ExtBidPrebidFloors{ + FloorValue: 5, + FloorCurrency: "EUR", + }, + }, + }, + }, + impCtx: ImpCtx{ + BidFloor: 2.2, + BidFloorCur: "EUR", + }, + currencyConversion: func(from, to string, value float64) (float64, error) { + return 10, nil + }, + }, + want: want{ + fv: 10, + frv: 10, + }, + }, + { + name: "floor_values_missing_in_bidExt_fallback_to_impctx", + args: args{ + bidExt: BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{}, + }, + }, + impCtx: ImpCtx{ + BidFloor: 2.2, + BidFloorCur: "USD", + }, + }, + want: want{ + fv: 2.2, + frv: 2.2, + }, + }, + { + name: "bidExt.Prebid_is_nil_fallback_to_impctx", + args: args{ + bidExt: BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: nil, + }, + }, + impCtx: ImpCtx{ + BidFloor: 2.2, + BidFloorCur: "USD", + }, + }, + want: want{ + fv: 2.2, + frv: 2.2, + }, + }, + { + name: "bidExt.Prebid.Floors_is_nil_fallback_to_impctx", + args: args{ + bidExt: BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + Floors: nil, + }, + }, + }, + impCtx: ImpCtx{ + BidFloor: 2.2, + BidFloorCur: "USD", + }, + }, + want: want{ + fv: 2.2, + frv: 2.2, + }, + }, + { + name: "currency_conversion_for_floor_values_in_impctx", + args: args{ + bidExt: BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + impCtx: ImpCtx{ + BidFloor: 5, + BidFloorCur: "EUR", + }, + currencyConversion: func(from, to string, value float64) (float64, error) { + return 10, nil + }, + }, + want: want{ + fv: 10, + frv: 10, + }, + }, + { + name: "floor_values_not_set_in_both_bidExt_and_impctx", + args: args{ + bidExt: BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + impCtx: ImpCtx{}, + currencyConversion: func(from, to string, value float64) (float64, error) { + return 10, nil + }, + }, + want: want{ + fv: 0, + frv: 0, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fv, frv := GetBidLevelFloorsDetails(tt.args.bidExt, tt.args.impCtx, tt.args.currencyConversion) + assert.Equal(t, tt.want.fv, fv, tt.name) + assert.Equal(t, tt.want.frv, frv, tt.name) + }) + } +} + +func Test_getFloorsDetails(t *testing.T) { + type args struct { + bidResponseExt openrtb_ext.ExtBidResponse + } + tests := []struct { + name string + args args + floorDetails FloorsDetails + }{ + { + name: "no_responseExt", + args: args{}, + floorDetails: FloorsDetails{}, + }, + { + name: "empty_responseExt", + args: args{ + bidResponseExt: openrtb_ext.ExtBidResponse{}, + }, + floorDetails: FloorsDetails{}, + }, + { + name: "empty_prebid_in_responseExt", + args: args{ + bidResponseExt: openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{}, + }, + }, + floorDetails: FloorsDetails{}, + }, + { + name: "empty_prebidfloors_in_responseExt", + args: args{ + bidResponseExt: openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + Floors: &openrtb_ext.PriceFloorRules{}, + }, + }, + }, + floorDetails: FloorsDetails{}, + }, + { + name: "no_enforced_floors_data_in_responseExt", + args: args{ + bidResponseExt: openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{}, + PriceFloorLocation: openrtb_ext.FetchLocation, + }, + }, + }, + }, + floorDetails: FloorsDetails{ + Skipfloors: nil, + FloorType: SoftFloor, + FloorSource: ptrutil.ToPtr(2), + FloorModelVersion: "", + }, + }, + { + name: "no_modelsgroups_floors_data_in_responseExt", + args: args{ + bidResponseExt: openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{}, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + }, + }, + }, + }, + }, + floorDetails: FloorsDetails{ + Skipfloors: nil, + FloorType: HardFloor, + FloorSource: ptrutil.ToPtr(2), + FloorModelVersion: "", + }, + }, + { + name: "no_skipped_floors_data_in_responseExt", + args: args{ + bidResponseExt: openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "version 1", + }, + }, + }, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + }, + }, + }, + }, + }, + floorDetails: FloorsDetails{ + Skipfloors: nil, + FloorType: HardFloor, + FloorSource: ptrutil.ToPtr(2), + FloorModelVersion: "version 1", + }, + }, + { + name: "all_floors_data_in_responseExt", + args: args{ + bidResponseExt: openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Skipped: ptrutil.ToPtr(true), + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "version 1", + }, + }, + }, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + }, + }, + }, + }, + }, + floorDetails: FloorsDetails{ + Skipfloors: ptrutil.ToPtr(1), + FloorType: HardFloor, + FloorSource: ptrutil.ToPtr(2), + FloorModelVersion: "version 1", + }, + }, + { + name: "floor_provider_present", + args: args{ + bidResponseExt: openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Skipped: ptrutil.ToPtr(true), + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "version 1", + }, + }, + FloorProvider: "provider", + }, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + }, + }, + }, + }, + }, + floorDetails: FloorsDetails{ + Skipfloors: ptrutil.ToPtr(1), + FloorType: HardFloor, + FloorSource: ptrutil.ToPtr(2), + FloorModelVersion: "version 1", + FloorProvider: "provider", + }, + }, + { + name: "floor_fetch_status_absent_in_FloorSourceMap", + args: args{ + bidResponseExt: openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Skipped: ptrutil.ToPtr(true), + FetchStatus: "invalid", + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "version 1", + }, + }, + FloorProvider: "provider", + }, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + }, + }, + }, + }, + }, + floorDetails: FloorsDetails{ + Skipfloors: ptrutil.ToPtr(1), + FloorType: HardFloor, + FloorSource: ptrutil.ToPtr(2), + FloorModelVersion: "version 1", + FloorProvider: "provider", + }, + }, + { + name: "floor_fetch_status_present_in_FloorSourceMap", + args: args{ + bidResponseExt: openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Skipped: ptrutil.ToPtr(true), + FetchStatus: openrtb_ext.FetchError, + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "version 1", + }, + }, + FloorProvider: "provider", + }, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + }, + }, + }, + }, + }, + floorDetails: FloorsDetails{ + Skipfloors: ptrutil.ToPtr(1), + FloorType: HardFloor, + FloorSource: ptrutil.ToPtr(2), + FloorModelVersion: "version 1", + FloorProvider: "provider", + FloorFetchStatus: ptrutil.ToPtr(2), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + floorsDetails := GetFloorsDetails(tt.args.bidResponseExt) + assert.Equal(t, tt.floorDetails, floorsDetails, tt.name) + }) + } +} + +func TestGetKGPSV(t *testing.T) { + type args struct { + bid openrtb2.Bid + bidderMeta PartnerData + adformat string + tagId string + div string + source string + } + tests := []struct { + name string + args args + kgpv string + kgpsv string + }{ + { + name: "default bid not regex", + args: args{ + bidderMeta: PartnerData{ + KGPV: "kgpv", + }, + }, + kgpv: "kgpv", + kgpsv: "kgpv", + }, + { + name: "default bid regex", + args: args{ + bidderMeta: PartnerData{ + KGPV: "kgpv", + IsRegex: true, + }, + }, + kgpv: "kgpv", + kgpsv: "", + }, + { + name: "only kgpsv found in partnerData", + args: args{ + bidderMeta: PartnerData{ + MatchedSlot: "kgpsv", + IsRegex: true, + }, + }, + kgpv: "kgpsv", + kgpsv: "kgpsv", + }, + { + name: "valid bid found in partnerData and regex true", + args: args{ + bid: openrtb2.Bid{ + Price: 1, + DealID: "deal", + W: 250, + H: 300, + }, + bidderMeta: PartnerData{ + KGPV: "kgpv", + MatchedSlot: "kgpsv", + IsRegex: true, + }, + }, + kgpv: "kgpv", + kgpsv: "kgpsv", + }, + { + name: "valid bid and regex false", + args: args{ + bid: openrtb2.Bid{ + Price: 1, + DealID: "deal", + W: 250, + H: 300, + }, + bidderMeta: PartnerData{ + KGPV: "kgpv", + MatchedSlot: "kgpsv", + IsRegex: false, + }, + }, + kgpv: "kgpv", + kgpsv: "kgpv", + }, + { + name: "KGPV and KGP not present in partnerData,regex false and adformat is video", + args: args{ + bid: openrtb2.Bid{ + Price: 1, + DealID: "deal", + W: 250, + H: 300, + }, + adformat: Video, + }, + kgpv: "", + kgpsv: "", + }, + { + name: "KGPV not present in partnerData,regex false and adformat is video", + args: args{ + bid: openrtb2.Bid{ + Price: 1, + DealID: "deal", + W: 250, + H: 300, + }, + adformat: Video, + bidderMeta: PartnerData{ + KGP: "_AU_@_W_x_H_", + }, + tagId: "adunit", + }, + kgpv: "adunit@0x0", + kgpsv: "adunit@0x0", + }, + { + name: "KGPV not present in partnerData,regex false and adformat is banner", + args: args{ + bid: openrtb2.Bid{ + Price: 1, + DealID: "deal", + W: 250, + H: 300, + }, + adformat: Banner, + bidderMeta: PartnerData{ + KGP: "_AU_@_W_x_H_", + MatchedSlot: "matchedSlot", + }, + tagId: "adunit", + }, + kgpv: "adunit@250x300", + kgpsv: "adunit@250x300", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := GetKGPSV(tt.args.bid, tt.args.bidderMeta, tt.args.adformat, tt.args.tagId, tt.args.div, tt.args.source) + if got != tt.kgpv { + t.Errorf("GetKGPSV() got = %v, want %v", got, tt.kgpv) + } + if got1 != tt.kgpsv { + t.Errorf("GetKGPSV() got1 = %v, want %v", got1, tt.kgpsv) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/models/video.go b/modules/pubmatic/openwrap/models/video.go new file mode 100644 index 00000000000..fa87aff6a2d --- /dev/null +++ b/modules/pubmatic/openwrap/models/video.go @@ -0,0 +1,34 @@ +package models + +const ( + //VideoVASTTag video VAST parameter constant + VideoVASTTag = "./VAST" + //VideoVASTVersion video version parameter constant + VideoVASTVersion = "version" + //VideoVASTVersion2_0 video version 2.0 parameter constant + VideoVASTVersion2_0 = "2.0" + //VideoVASTAdWrapperTag video ad/wrapper element constant + VideoVASTAdWrapperTag = "./Ad/Wrapper" + //VideoVASTAdInLineTag video ad/inline element constant + VideoVASTAdInLineTag = "./Ad/InLine" + //VideoExtensionsTag video extensions element constant + VideoExtensionsTag = "Extensions" + //VideoExtensionTag video extension element constant + VideoExtensionTag = "Extension" + //VideoPricingTag video pricing element constant + VideoPricingTag = "Pricing" + //VideoPricingModel video model attribute constant + VideoPricingModel = "model" + //VideoPricingModelCPM video cpm attribute value constant + VideoPricingModelCPM = "CPM" + //VideoPricingCurrencyUSD video USD default currency constant + VideoPricingCurrencyUSD = "USD" + //VideoPricingCurrency video currency constant + VideoPricingCurrency = "currency" + //VideoTagLookupStart video xpath constant + VideoTagLookupStart = "./" + //VideoTagForwardSlash video forward slash for xpath constant + VideoTagForwardSlash = "/" + //VideoVAST2ExtensionPriceElement video parameter constant + VideoVAST2ExtensionPriceElement = VideoTagLookupStart + VideoExtensionTag + VideoTagForwardSlash + VideoPricingTag +) diff --git a/modules/pubmatic/openwrap/module.go b/modules/pubmatic/openwrap/module.go new file mode 100644 index 00000000000..fdc3fba7d93 --- /dev/null +++ b/modules/pubmatic/openwrap/module.go @@ -0,0 +1,93 @@ +package openwrap + +import ( + "context" + "encoding/json" + "runtime/debug" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/moduledeps" +) + +// init openwrap module and its dependecies like config, cache, db connection, bidder cfg, etc. +func Builder(rawCfg json.RawMessage, deps moduledeps.ModuleDeps) (interface{}, error) { + return initOpenWrap(rawCfg, deps) +} + +// temporary openwrap changes to support non-pbs apis like openrtb/2.5, openrtb/amp, etc +// temporary openwrap changes to support non-ortb fields like request.ext.wrapper +func (m OpenWrap) HandleEntrypointHook( + ctx context.Context, + miCtx hookstage.ModuleInvocationContext, + payload hookstage.EntrypointPayload, +) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + defer func() { + if r := recover(); r != nil { + m.metricEngine.RecordOpenWrapServerPanicStats(m.cfg.Server.HostName, "HandleEntrypointHook") + glog.Error("body:" + string(payload.Body) + ". stacktrace:" + string(debug.Stack())) + } + }() + + return m.handleEntrypointHook(ctx, miCtx, payload) +} + +// changes to init the request ctx with profile and request details +func (m OpenWrap) HandleBeforeValidationHook( + ctx context.Context, + miCtx hookstage.ModuleInvocationContext, + payload hookstage.BeforeValidationRequestPayload, +) (hookstage.HookResult[hookstage.BeforeValidationRequestPayload], error) { + defer func() { + if r := recover(); r != nil { + m.metricEngine.RecordOpenWrapServerPanicStats(m.cfg.Server.HostName, "HandleBeforeValidationHook") + request, err := json.Marshal(payload) + if err != nil { + glog.Error("request:" + string(request) + ". err: " + err.Error() + ". stacktrace:" + string(debug.Stack())) + return + } + glog.Error("request:" + string(request) + ". stacktrace:" + string(debug.Stack())) + } + }() + + return m.handleBeforeValidationHook(ctx, miCtx, payload) +} + +func (m OpenWrap) HandleAllProcessedBidResponsesHook( + ctx context.Context, + miCtx hookstage.ModuleInvocationContext, + payload hookstage.AllProcessedBidResponsesPayload, +) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) { + defer func() { + if r := recover(); r != nil { + m.metricEngine.RecordOpenWrapServerPanicStats(m.cfg.Server.HostName, "HandleAllProcessedBidResponsesHook") + request, err := json.Marshal(payload) + if err != nil { + glog.Error("request:" + string(request) + ". err: " + err.Error() + ". stacktrace:" + string(debug.Stack())) + } + glog.Error("request:" + string(request) + ". stacktrace:" + string(debug.Stack())) + } + }() + + return m.handleAllProcessedBidResponsesHook(ctx, miCtx, payload) +} + +func (m OpenWrap) HandleAuctionResponseHook( + ctx context.Context, + miCtx hookstage.ModuleInvocationContext, + payload hookstage.AuctionResponsePayload, +) (hookstage.HookResult[hookstage.AuctionResponsePayload], error) { + defer func() { + if r := recover(); r != nil { + m.metricEngine.RecordOpenWrapServerPanicStats(m.cfg.Server.HostName, "HandleAuctionResponseHook") + response, err := json.Marshal(payload) + if err != nil { + glog.Error("response:" + string(response) + ". err: " + err.Error() + ". stacktrace:" + string(debug.Stack())) + return + } + glog.Error("response:" + string(response) + ". stacktrace:" + string(debug.Stack())) + } + }() + + return m.handleAuctionResponseHook(ctx, miCtx, payload) +} diff --git a/modules/pubmatic/openwrap/nonbids.go b/modules/pubmatic/openwrap/nonbids.go new file mode 100644 index 00000000000..a00ca52fd7b --- /dev/null +++ b/modules/pubmatic/openwrap/nonbids.go @@ -0,0 +1,99 @@ +package openwrap + +import ( + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// prepareSeatNonBids forms the rctx.SeatNonBids map from rctx values +// currently, this function prepares and returns nonbids for partner-throttle and slot-not-mapped errors +func prepareSeatNonBids(rctx models.RequestCtx) map[string][]openrtb_ext.NonBid { + + seatNonBids := make(map[string][]openrtb_ext.NonBid, 0) + for impID, impCtx := range rctx.ImpBidCtx { + // seat-non-bid for partner-throttled error + for bidder := range rctx.AdapterThrottleMap { + seatNonBids[bidder] = append(seatNonBids[bidder], openrtb_ext.NonBid{ + ImpId: impID, + StatusCode: int(exchange.RequestBlockedPartnerThrottle), + }) + } + // seat-non-bid for slot-not-mapped error + // Note : Throttled partner will not be a part of impCtx.NonMapped + for bidder := range impCtx.NonMapped { + seatNonBids[bidder] = append(seatNonBids[bidder], openrtb_ext.NonBid{ + ImpId: impID, + StatusCode: int(exchange.RequestBlockedSlotNotMapped), + }) + } + } + return seatNonBids +} + +// addSeatNonBidsInResponseExt adds the rctx.SeatNonBids in the response-ext +func addSeatNonBidsInResponseExt(rctx models.RequestCtx, responseExt *openrtb_ext.ExtBidResponse) { + if len(rctx.SeatNonBids) == 0 { + return + } + + if responseExt.Prebid == nil { + responseExt.Prebid = new(openrtb_ext.ExtResponsePrebid) + } + + if responseExt.Prebid.SeatNonBid == nil { + responseExt.Prebid.SeatNonBid = make([]openrtb_ext.SeatNonBid, 0) + } + + for index, seatnonbid := range responseExt.Prebid.SeatNonBid { + // if response-ext contains list of nonbids for bidder then + // add the rctx-nonbids to the same list + if nonBids, found := rctx.SeatNonBids[seatnonbid.Seat]; found { + responseExt.Prebid.SeatNonBid[index].NonBid = append(responseExt.Prebid.SeatNonBid[index].NonBid, nonBids...) + delete(rctx.SeatNonBids, seatnonbid.Seat) + } + } + + // at this point, rctx.SeatNonBids will contain nonbids for only those seat/bidder which are not part of response-ext + for seat, nonBids := range rctx.SeatNonBids { + responseExt.Prebid.SeatNonBid = append(responseExt.Prebid.SeatNonBid, + openrtb_ext.SeatNonBid{ + Seat: seat, + NonBid: nonBids, + }) + } +} + +// addLostToDealBidNonBRCode function sets the NonBR code of all lost-bids not satisfying dealTier to LossBidLostToDealBid +func addLostToDealBidNonBRCode(rctx *models.RequestCtx) { + if !rctx.SupportDeals { + return + } + + for impID := range rctx.ImpBidCtx { + winBid, ok := rctx.WinningBids[impID] + if !ok { + continue + } + + for bidID, bidCtx := range rctx.ImpBidCtx[impID].BidCtx { + // do not update NonBR for winning bid + if winBid.ID == bidID { + continue + } + + bidDealTierSatisfied := false + if bidCtx.BidExt.Prebid != nil { + bidDealTierSatisfied = bidCtx.BidExt.Prebid.DealTierSatisfied + } + // do not update NonBr if lost-bid satisfies dealTier + // because it can have NonBr reason as LossBidLostToHigherBid + if bidDealTierSatisfied { + continue + } + bidCtx.BidExt.Nbr = GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid) + rctx.ImpBidCtx[impID].BidCtx[bidID] = bidCtx + } + } +} diff --git a/modules/pubmatic/openwrap/nonbids_test.go b/modules/pubmatic/openwrap/nonbids_test.go new file mode 100644 index 00000000000..40b257fd938 --- /dev/null +++ b/modules/pubmatic/openwrap/nonbids_test.go @@ -0,0 +1,829 @@ +package openwrap + +import ( + "testing" + + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestPrepareSeatNonBids(t *testing.T) { + type args struct { + rctx models.RequestCtx + } + + tests := []struct { + name string + args args + seatNonBids map[string][]openrtb_ext.NonBid + }{ + { + name: "empty_impbidctx", + args: args{ + rctx: models.RequestCtx{ + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + }, + }, + seatNonBids: make(map[string][]openrtb_ext.NonBid), + }, + { + name: "empty_seatnonbids", + args: args{ + rctx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + ImpID: "imp1", + }, + }, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + }, + }, + seatNonBids: make(map[string][]openrtb_ext.NonBid), + }, + { + name: "partner_throttled_nonbids", + args: args{ + rctx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + ImpID: "imp1", + }, + }, + AdapterThrottleMap: map[string]struct{}{ + "pubmatic": {}, + }, + SeatNonBids: map[string][]openrtb_ext.NonBid{}, + }, + }, + seatNonBids: map[string][]openrtb_ext.NonBid{ + "pubmatic": { + openrtb_ext.NonBid{ + ImpId: "imp1", + StatusCode: int(exchange.RequestBlockedPartnerThrottle), + }, + }, + }, + }, + { + name: "slot_not_mapped_nonbids", + args: args{ + rctx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + NonMapped: map[string]struct{}{ + "pubmatic": {}, + "appnexus": {}, + }, + }, + }, + SeatNonBids: map[string][]openrtb_ext.NonBid{ + "pubmatic": { + { + ImpId: "imp2", + StatusCode: 2, + }, + }, + }, + }, + }, + seatNonBids: map[string][]openrtb_ext.NonBid{ + "pubmatic": { + { + ImpId: "imp1", + StatusCode: int(exchange.RequestBlockedSlotNotMapped), + }, + }, + "appnexus": { + { + ImpId: "imp1", + StatusCode: int(exchange.RequestBlockedSlotNotMapped), + }, + }, + }, + }, + { + name: "slot_not_mapped_plus_partner_throttled_nonbids", + args: args{ + rctx: models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + NonMapped: map[string]struct{}{ + "pubmatic": {}, + }, + }, + "imp2": {}, + }, + AdapterThrottleMap: map[string]struct{}{ + "appnexus": {}, + }, + }, + }, + seatNonBids: map[string][]openrtb_ext.NonBid{ + "pubmatic": { + { + ImpId: "imp1", + StatusCode: int(exchange.RequestBlockedSlotNotMapped), + }, + }, + "appnexus": { + { + ImpId: "imp2", + StatusCode: int(exchange.RequestBlockedPartnerThrottle), + }, + { + ImpId: "imp1", + StatusCode: int(exchange.RequestBlockedPartnerThrottle), + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + seatNonBids := prepareSeatNonBids(tt.args.rctx) + assert.Equal(t, len(seatNonBids), len(tt.seatNonBids)) + for k, v := range seatNonBids { + // ignore order of elements in slice while comparing + assert.ElementsMatch(t, v, tt.seatNonBids[k], tt.name) + } + }) + } +} + +func TestAddSeatNonBidsInResponseExt(t *testing.T) { + type args struct { + rctx models.RequestCtx + responseExt *openrtb_ext.ExtBidResponse + } + + tests := []struct { + name string + args args + want *openrtb_ext.ExtBidResponse + }{ + { + name: "empty_rtcx_seatnonbids", + args: args{ + rctx: models.RequestCtx{}, + responseExt: &openrtb_ext.ExtBidResponse{ + Prebid: nil, + }, + }, + want: &openrtb_ext.ExtBidResponse{ + Prebid: nil, + }, + }, + { + name: "response_ext_prebid_is_nil", + args: args{ + rctx: models.RequestCtx{ + SeatNonBids: map[string][]openrtb_ext.NonBid{ + "pubmatic": { + openrtb_ext.NonBid{ + ImpId: "imp1", + StatusCode: 1, + }, + }, + }, + }, + responseExt: &openrtb_ext.ExtBidResponse{ + Prebid: nil, + }, + }, + want: &openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 1, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + }, + { + name: "prebid_exist_but_seatnonbid_is_empty_in_ext", + args: args{ + rctx: models.RequestCtx{ + SeatNonBids: map[string][]openrtb_ext.NonBid{ + "pubmatic": { + openrtb_ext.NonBid{ + ImpId: "imp1", + StatusCode: 1, + }, + openrtb_ext.NonBid{ + ImpId: "imp2", + StatusCode: 2, + }, + }, + }, + }, + responseExt: &openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + AuctionTimestamp: 100, + }, + }, + }, + want: &openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + AuctionTimestamp: 100, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 1, + }, + { + ImpId: "imp2", + StatusCode: 2, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + }, + { + name: "nonbid_exist_in_rctx_and_in_ext_for_specific_bidder", + args: args{ + rctx: models.RequestCtx{ + SeatNonBids: map[string][]openrtb_ext.NonBid{ + "pubmatic": { + openrtb_ext.NonBid{ + ImpId: "imp1", + StatusCode: 1, + }, + }, + }, + }, + responseExt: &openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + AuctionTimestamp: 100, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp2", + StatusCode: 2, + }, + }, + }, + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 1, + }, + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + AuctionTimestamp: 100, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp2", + StatusCode: 2, + }, + { + ImpId: "imp1", + StatusCode: 1, + }, + }, + }, + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 1, + }, + }, + }, + }, + }, + }, + }, + { + name: "nonbid_exist_in_rctx_but_not_in_ext_for_specific_bidder", + args: args{ + rctx: models.RequestCtx{ + SeatNonBids: map[string][]openrtb_ext.NonBid{ + "pubmatic": { + openrtb_ext.NonBid{ + ImpId: "imp1", + StatusCode: 1, + }, + }, + "appnexus": { + openrtb_ext.NonBid{ + ImpId: "imp1", + StatusCode: 1, + }, + }, + }, + }, + responseExt: &openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + AuctionTimestamp: 100, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp2", + StatusCode: 2, + }, + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + AuctionTimestamp: 100, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp2", + StatusCode: 2, + }, + { + ImpId: "imp1", + StatusCode: 1, + }, + }, + }, + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 1, + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + addSeatNonBidsInResponseExt(tt.args.rctx, tt.args.responseExt) + assert.Equal(t, tt.want, tt.args.responseExt, tt.name) + }) + } +} + +func TestAddLostToDealBidNonBRCode(t *testing.T) { + tests := []struct { + name string + rctx *models.RequestCtx + impBidCtx map[string]models.ImpCtx + }{ + { + name: "support deal flag is false", + rctx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + }, + }, + }, + }, + }, + }, + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "no winning bid for imp so dont update NonBR code", + rctx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + }, + }, + }, + }, + }, + SupportDeals: true, + }, + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "do not update LossBidLostToHigherBid NonBR code if bid satisifies dealTier", + rctx: &models.RequestCtx{ + WinningBids: map[string]models.OwBid{ + "imp1": { + ID: "bid-id-3", + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 50, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + }, + }, + "bid-id-3": { + BidExt: models.BidExt{ + NetECPM: 100, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + }, + }, + }, + }, + }, + }, + }, + SupportDeals: true, + }, + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 50, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + }, + }, + "bid-id-3": { + BidExt: models.BidExt{ + NetECPM: 100, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "update LossBidLostToHigherBid NonBR code if bid not satisifies dealTier", + rctx: &models.RequestCtx{ + WinningBids: map[string]models.OwBid{ + "imp1": { + ID: "bid-id-3", + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 100, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + }, + }, + "bid-id-3": { + BidExt: models.BidExt{ + NetECPM: 5, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + }, + }, + }, + }, + }, + }, + }, + SupportDeals: true, + }, + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 100, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + }, + }, + "bid-id-3": { + BidExt: models.BidExt{ + NetECPM: 5, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "test for multiple impression", + rctx: &models.RequestCtx{ + WinningBids: map[string]models.OwBid{ + "imp1": { + ID: "bid-id-3", + }, + "imp2": { + ID: "bid-id-2", + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToHigherBid), + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 100, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + }, + }, + "bid-id-3": { + BidExt: models.BidExt{ + NetECPM: 5, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + }, + }, + }, + }, + }, + }, + "imp2": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 100, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + }, + }, + }, + }, + "bid-id-3": { + BidExt: models.BidExt{ + NetECPM: 5, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + }, + }, + }, + }, + }, + SupportDeals: true, + }, + impBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 100, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + }, + }, + "bid-id-3": { + BidExt: models.BidExt{ + NetECPM: 5, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + }, + }, + }, + }, + }, + }, + "imp2": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + NetECPM: 10, + ExtBid: openrtb_ext.ExtBid{ + + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + NetECPM: 100, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: true, + }, + }, + }, + }, + "bid-id-3": { + BidExt: models.BidExt{ + NetECPM: 5, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + DealTierSatisfied: false, + }, + }, + Nbr: GetNonBidStatusCodePtr(openrtb3.LossBidLostToDealBid), + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + addLostToDealBidNonBRCode(tt.rctx) + assert.Equal(t, tt.impBidCtx, tt.rctx.ImpBidCtx, tt.name) + }) + } +} diff --git a/modules/pubmatic/openwrap/openwrap.go b/modules/pubmatic/openwrap/openwrap.go new file mode 100644 index 00000000000..d673aca8ee0 --- /dev/null +++ b/modules/pubmatic/openwrap/openwrap.go @@ -0,0 +1,109 @@ +package openwrap + +import ( + "database/sql" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "github.com/golang/glog" + gocache "github.com/patrickmn/go-cache" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/modules/moduledeps" + ow_adapters "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adapters" + cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + ow_gocache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache/gocache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database/mysql" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/fullscreenclickability" + metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + metrics_cfg "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/tbf" +) + +const ( + CACHE_EXPIRY_ROUTINE_RUN_INTERVAL = 1 * time.Minute +) + +type OpenWrap struct { + cfg config.Config + cache cache.Cache + metricEngine metrics.MetricsEngine + currencyConversion currency.Conversions +} + +func initOpenWrap(rawCfg json.RawMessage, moduleDeps moduledeps.ModuleDeps) (OpenWrap, error) { + cfg := config.Config{} + + err := json.Unmarshal(rawCfg, &cfg) + if err != nil { + return OpenWrap{}, fmt.Errorf("invalid openwrap config: %v", err) + } + patchConfig(&cfg) + + glog.Info("Connecting to OpenWrap database...") + mysqlDriver, err := open("mysql", cfg.Database) + if err != nil { + return OpenWrap{}, fmt.Errorf("failed to open db connection: %v", err) + } + db := mysql.New(mysqlDriver, cfg.Database) + + // NYC_TODO: replace this with freecache and use concrete structure + cache := gocache.New(time.Duration(cfg.Cache.CacheDefaultExpiry)*time.Second, CACHE_EXPIRY_ROUTINE_RUN_INTERVAL) + if cache == nil { + return OpenWrap{}, errors.New("error while initializing cache") + } + + // NYC_TODO: remove this dependency + if err := ow_adapters.InitBidders(cfg); err != nil { + return OpenWrap{}, errors.New("error while initializing bidder params") + } + + metricEngine, err := metrics_cfg.NewMetricsEngine(&cfg, moduleDeps.MetricsCfg, moduleDeps.MetricsRegistry) + if err != nil { + return OpenWrap{}, fmt.Errorf("error while initializing metrics-engine: %v", err) + } + + owCache := ow_gocache.New(cache, db, cfg.Cache, &metricEngine) + + // Init FSC and related services + fullscreenclickability.Init(owCache, cfg.Cache.CacheDefaultExpiry) + + // Init TBF (tracking-beacon-first) feature related services + tbf.Init(cfg.Cache.CacheDefaultExpiry, owCache) + + return OpenWrap{ + cfg: cfg, + cache: owCache, + metricEngine: &metricEngine, + currencyConversion: moduleDeps.RateConvertor.Rates(), + }, nil +} + +func open(driverName string, cfg config.Database) (*sql.DB, error) { + dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", cfg.User, cfg.Pass, cfg.Host, cfg.Port, cfg.Database) + + db, err := sql.Open(driverName, dataSourceName) + if err != nil { + return nil, err + } + + db.SetMaxIdleConns(cfg.IdleConnection) + db.SetMaxOpenConns(cfg.MaxConnection) + db.SetConnMaxLifetime(time.Second * time.Duration(cfg.ConnMaxLifeTime)) + + err = db.Ping() + if err != nil { + return nil, err + } + + return db, nil +} + +func patchConfig(cfg *config.Config) { + cfg.Server.HostName = GetHostName() + models.TrackerCallWrapOMActive = strings.Replace(models.TrackerCallWrapOMActive, "${OMScript}", cfg.PixelView.OMScript, 1) +} diff --git a/modules/pubmatic/openwrap/openwrap_sshb.go b/modules/pubmatic/openwrap/openwrap_sshb.go new file mode 100644 index 00000000000..a2bcb8363b5 --- /dev/null +++ b/modules/pubmatic/openwrap/openwrap_sshb.go @@ -0,0 +1,38 @@ +package openwrap + +import ( + cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" +) + +// GetConfig Temporary function to expose config to SSHB +func (ow OpenWrap) GetConfig() config.Config { + return ow.cfg + +} + +// GetCache Temporary function to expose cache to SSHB +func (ow OpenWrap) GetCache() cache.Cache { + return ow.cache +} + +// GetMetricEngine Temporary function to expose mertics to SSHB +func (ow OpenWrap) GetMetricEngine() metrics.MetricsEngine { + return ow.metricEngine +} + +// SetConfig Temporary function to expose config to SSHB +func (ow *OpenWrap) SetConfig(c config.Config) { + ow.cfg = c +} + +// GetCache Temporary function to expose cache to SSHB +func (ow *OpenWrap) SetCache(c cache.Cache) { + ow.cache = c +} + +// GetMetricEngine Temporary function to expose mertics to SSHB +func (ow *OpenWrap) SetMetricEngine(m metrics.MetricsEngine) { + ow.metricEngine = m +} diff --git a/modules/pubmatic/openwrap/price_granularity.go b/modules/pubmatic/openwrap/price_granularity.go new file mode 100644 index 00000000000..5cc74653d19 --- /dev/null +++ b/modules/pubmatic/openwrap/price_granularity.go @@ -0,0 +1,56 @@ +package openwrap + +import ( + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" +) + +func computePriceGranularity(rctx models.RequestCtx) (openrtb_ext.PriceGranularity, error) { + //Get the value of priceGranularity from config + priceGranularity := models.GetVersionLevelPropertyFromPartnerConfig(rctx.PartnerConfigMap, models.PriceGranularityKey) + + if priceGranularity == "" || (priceGranularity == models.PriceGranularityCustom && !rctx.IsCTVRequest) { + // If it is empty then use default value as 'auto' + // If it is custom but not CTV request then use default value as 'a + priceGranularity = "auto" + } + if rctx.IsTestRequest > 0 && rctx.IsCTVRequest { + //OTT-603: Adding test flag check + priceGranularity = "testpg" + } + + // OTT-769: determine custom pg object based on customPriceGranularityValue config + // Expected that this check with be true iff platform is video / isCTVAPIRequest + if priceGranularity == models.PriceGranularityCustom { + customPriceGranularityValue := models.GetVersionLevelPropertyFromPartnerConfig(rctx.PartnerConfigMap, models.PriceGranularityCustomConfig) + pgObject, err := newCustomPriceGranuality(customPriceGranularityValue) + return pgObject, err + } + + // OTT-769: (Backword compatibilty) compute based on legacy string (auto, med) + pgObject, _ := openrtb_ext.NewPriceGranularityFromLegacyID(priceGranularity) + + return pgObject, nil +} + +// newCustomPriceGranuality constructs the Custom PriceGranularity Object based on input +// customPGValue +// if pg ranges are not present inside customPGValue then this function by default +// returns Medium Price Granularity Object +// So, caller of this function must ensure that customPGValue has valid pg ranges +// Optimization (Not implemented) : we can think of - only do unmarshal once if haven't done before +func newCustomPriceGranuality(customPGValue string) (openrtb_ext.PriceGranularity, error) { + // Assumptions + // 1. customPriceGranularityValue will never be empty + // 2. customPriceGranularityValue will not be legacy string viz. auto, dense + // 3. ranges are specified inside customPriceGranularityValue + pg := openrtb_ext.PriceGranularity{} + err := pg.UnmarshalJSON([]byte(customPGValue)) + if err != nil { + return pg, err + } + // Overwrite always to 2 + pg.Precision = ptrutil.ToPtr(2) + return pg, nil +} diff --git a/modules/pubmatic/openwrap/price_granularity_test.go b/modules/pubmatic/openwrap/price_granularity_test.go new file mode 100644 index 00000000000..37a2006be31 --- /dev/null +++ b/modules/pubmatic/openwrap/price_granularity_test.go @@ -0,0 +1,256 @@ +package openwrap + +import ( + "fmt" + "testing" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +var priceGranularityMed = openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{{ + Min: 0, + Max: 20, + Increment: 0.1}}, +} + +var priceGranularityAuto = openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{ + { + Min: 0, + Max: 5, + Increment: 0.05, + }, + { + Min: 5, + Max: 10, + Increment: 0.1, + }, + { + Min: 10, + Max: 20, + Increment: 0.5, + }, + }, +} + +var priceGranularityTestPG = openrtb_ext.PriceGranularity{ + Test: true, + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{{ + Min: 0, + Max: 50, + Increment: 50}}, +} + +var priceGranularityDense = openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{ + { + Min: 0, + Max: 3, + Increment: 0.01, + }, + { + Min: 3, + Max: 8, + Increment: 0.05, + }, + { + Min: 8, + Max: 20, + Increment: 0.5, + }, + }, +} + +func TestComputePriceGranularity(t *testing.T) { + type args struct { + rctx models.RequestCtx + } + tests := []struct { + name string + args args + want openrtb_ext.PriceGranularity + wantErr bool + }{ + { + name: "dense_price_granularity_for_in_app_platform", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.PriceGranularityKey: "dense", + }, + }, + }, + }, + want: priceGranularityDense, + wantErr: false, + }, + { + name: "no_pricegranularity_in_db_defaults_to_auto", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: nil, + }, + }, + want: priceGranularityAuto, // auto PG Object + wantErr: false, + }, { + name: "custompg_OpenRTB_V25_API_defaults_to_auto", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.PriceGranularityKey: "custom", + }, + }, + IsCTVRequest: false, + }, + }, + want: priceGranularityAuto, + wantErr: false, + }, { + name: "testreq_ctv_expect_testpg", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 1, + IsCTVRequest: true, + }, + }, + want: priceGranularityTestPG, + wantErr: false, + }, { + name: "custompg_ctvapi", + args: args{ + rctx: models.RequestCtx{ + IsCTVRequest: true, + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.PriceGranularityKey: "custom", + models.PriceGranularityCustomConfig: `{ "ranges": [{"min": 0, "max":2, "increment" : 1}]}`, + }, + }, + }, + }, + want: openrtb_ext.PriceGranularity{ + Test: false, + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{ + { + Min: 0, Max: 2, Increment: 1, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := computePriceGranularity(tt.args.rctx) + if (err != nil) != tt.wantErr { + assert.Equal(t, tt.wantErr, err != nil) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestNewCustomPriceGranuality(t *testing.T) { + type args struct { + customPGValue string + } + tests := []struct { + name string + args args + want openrtb_ext.PriceGranularity + wantErr bool + }{ + { + name: "empty_pg_expect_default_medium_pg", + args: args{customPGValue: ""}, + want: openrtb_ext.PriceGranularity{}, + wantErr: true, + }, + { + name: "always_have_precision_2", + args: args{customPGValue: `{ "precision": 3, "ranges":[{ "min" : 0.01, "max" : 0.99, "increment" : 0.10}] }`}, + want: openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{{ + Min: 0.01, + Max: 0.99, + Increment: 0.10, + }}, + }, + wantErr: false, + }, + { + // not expected case as DB will never contain pg without range + name: "no_ranges_defaults_to_medium_pg", + args: args{customPGValue: `{}`}, + want: openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + }, + wantErr: false, + }, + { + name: "0_precision_overwrite_with_2", + args: args{customPGValue: `{ "precision": 0, "ranges":[{ "min" : 0.01, "max" : 0.99, "increment" : 0.10}] }`}, + want: openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{{ + Min: 0.01, + Max: 0.99, + Increment: 0.10, + }}, + }, + wantErr: false, + }, + { + + name: "precision_greater_than_max_decimal_figures_expect_2", + args: args{customPGValue: fmt.Sprintf(`{ "precision": %v, "ranges":[{ "min" : 0.01, "max" : 0.99, "increment" : 0.10}] }`, openrtb_ext.MaxDecimalFigures)}, + want: openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{{ + Min: 0.01, + Max: 0.99, + Increment: 0.10, + }}, + }, + wantErr: false, + }, + { + + name: "increment_less_than_0_error", + args: args{customPGValue: `{ "ranges":[{ "min" : 0.01, "max" : 0.99, "increment" : -1.0}] }`}, + want: openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{{ + Min: 0.01, + Max: 0.99, + Increment: -1, + }}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := newCustomPriceGranuality(tt.args.customPGValue) + if (err != nil) != tt.wantErr { + assert.Equal(t, tt.wantErr, err != nil) + return + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/processedauctionhook.go b/modules/pubmatic/openwrap/processedauctionhook.go new file mode 100644 index 00000000000..2108f67e07a --- /dev/null +++ b/modules/pubmatic/openwrap/processedauctionhook.go @@ -0,0 +1,38 @@ +package openwrap + +import ( + "context" + + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +func (m OpenWrap) HandleProcessedAuctionHook( + ctx context.Context, + moduleCtx hookstage.ModuleInvocationContext, + payload hookstage.ProcessedAuctionRequestPayload, +) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { + result := hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{} + result.ChangeSet = hookstage.ChangeSet[hookstage.ProcessedAuctionRequestPayload]{} + + if len(moduleCtx.ModuleContext) == 0 { + result.DebugMessages = append(result.DebugMessages, "error: module-ctx not found in handleBeforeValidationHook()") + return result, nil + } + rctx, ok := moduleCtx.ModuleContext["rctx"].(models.RequestCtx) + if !ok { + result.DebugMessages = append(result.DebugMessages, "error: request-ctx not found in handleBeforeValidationHook()") + return result, nil + } + + ip := rctx.IP + + result.ChangeSet.AddMutation(func(parp hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { + if parp.Request != nil && parp.Request.BidRequest.Device != nil && (parp.Request.BidRequest.Device.IP == "" && parp.Request.BidRequest.Device.IPv6 == "") { + parp.Request.BidRequest.Device.IP = ip + } + return parp, nil + }, hookstage.MutationUpdate, "update-device-ip") + + return result, nil +} diff --git a/modules/pubmatic/openwrap/profiledata.go b/modules/pubmatic/openwrap/profiledata.go new file mode 100644 index 00000000000..4f802728b21 --- /dev/null +++ b/modules/pubmatic/openwrap/profiledata.go @@ -0,0 +1,40 @@ +package openwrap + +import ( + "strconv" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func (m OpenWrap) getProfileData(rCtx models.RequestCtx, bidRequest openrtb2.BidRequest) (map[int]map[string]string, error) { + if rCtx.IsTestRequest == 2 { // skip db data for test=2 + //get platform from request, since test mode can be enabled for display and app platform only + var platform string // TODO: should we've some default platform value + if bidRequest.App != nil { + platform = models.PLATFORM_APP + } + + return getTestModePartnerConfigMap(platform, m.cfg.Timeout.HBTimeout, rCtx.DisplayID), nil + } + + return m.cache.GetPartnerConfigMap(rCtx.PubID, rCtx.ProfileID, rCtx.DisplayID, rCtx.Endpoint) +} + +func getTestModePartnerConfigMap(platform string, timeout int64, displayVersion int) map[int]map[string]string { + return map[int]map[string]string{ + 1: { + models.PARTNER_ID: models.PUBMATIC_PARTNER_ID_STRING, + models.PREBID_PARTNER_NAME: string(openrtb_ext.BidderPubmatic), + models.BidderCode: string(openrtb_ext.BidderPubmatic), + models.SERVER_SIDE_FLAG: models.PUBMATIC_SS_FLAG, + models.KEY_GEN_PATTERN: models.ADUNIT_SIZE_KGP, + models.TIMEOUT: strconv.Itoa(int(timeout)), + }, + -1: { + models.PLATFORM_KEY: platform, + models.DisplayVersionID: strconv.Itoa(displayVersion), + }, + } +} diff --git a/modules/pubmatic/openwrap/profiledata_test.go b/modules/pubmatic/openwrap/profiledata_test.go new file mode 100644 index 00000000000..ccd9d00fa09 --- /dev/null +++ b/modules/pubmatic/openwrap/profiledata_test.go @@ -0,0 +1,255 @@ +package openwrap + +import ( + "fmt" + "testing" + + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + mock_cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestOpenWrap_getProfileData(t *testing.T) { + ctrl := gomock.NewController(t) + mockCache := mock_cache.NewMockCache(ctrl) + defer ctrl.Finish() + + type fields struct { + cfg config.Config + cache cache.Cache + metricEngine metrics.MetricsEngine + } + type args struct { + rCtx models.RequestCtx + bidRequest openrtb2.BidRequest + } + tests := []struct { + name string + fields fields + args args + setup func() + want map[int]map[string]string + wantErr bool + }{ + { + name: "get_profile_data_for_test_mode_platform_is_APP", + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + HBTimeout: 100, + }, + }, + cache: mockCache, + }, + args: args{ + rCtx: models.RequestCtx{ + DisplayID: 1, + IsTestRequest: 2, + }, + bidRequest: openrtb2.BidRequest{ + App: &openrtb2.App{}, + }, + }, + want: map[int]map[string]string{ + 1: { + models.PARTNER_ID: models.PUBMATIC_PARTNER_ID_STRING, + models.PREBID_PARTNER_NAME: string(openrtb_ext.BidderPubmatic), + models.BidderCode: string(openrtb_ext.BidderPubmatic), + models.SERVER_SIDE_FLAG: models.PUBMATIC_SS_FLAG, + models.KEY_GEN_PATTERN: models.ADUNIT_SIZE_KGP, + models.TIMEOUT: "100", + }, + -1: { + models.PLATFORM_KEY: models.PLATFORM_APP, + models.DisplayVersionID: "1", + }, + }, + wantErr: false, + }, + { + name: "get_profile_data_for_test_mode_platform_is_other_than_APP", + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + HBTimeout: 100, + }, + }, + cache: mockCache, + }, + args: args{ + rCtx: models.RequestCtx{ + DisplayID: 1, + IsTestRequest: 2, + }, + bidRequest: openrtb2.BidRequest{ + App: nil, + }, + }, + want: map[int]map[string]string{ + 1: { + models.PARTNER_ID: models.PUBMATIC_PARTNER_ID_STRING, + models.PREBID_PARTNER_NAME: string(openrtb_ext.BidderPubmatic), + models.BidderCode: string(openrtb_ext.BidderPubmatic), + models.SERVER_SIDE_FLAG: models.PUBMATIC_SS_FLAG, + models.KEY_GEN_PATTERN: models.ADUNIT_SIZE_KGP, + models.TIMEOUT: "100", + }, + -1: { + models.PLATFORM_KEY: "", + models.DisplayVersionID: "1", + }, + }, + wantErr: false, + }, + { + name: "get_profile_data_for_the_non_test_request", + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + HBTimeout: 100, + }, + }, + cache: mockCache, + }, + args: args{ + rCtx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + Endpoint: models.PLATFORM_APP, + }, + bidRequest: openrtb2.BidRequest{ + App: nil, + }, + }, + setup: func() { + mockCache.EXPECT().GetPartnerConfigMap(5890, 123, 1, models.PLATFORM_APP).Return( + map[int]map[string]string{ + 1: { + 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", + }, + }, nil) + }, + want: map[int]map[string]string{ + 1: { + 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", + }, + }, + wantErr: false, + }, + { + name: "get_profile_data_for_non_test_request_but_cache_returned_error", + fields: fields{ + cfg: config.Config{ + Timeout: config.Timeout{ + HBTimeout: 100, + }, + }, + cache: mockCache, + }, + args: args{ + rCtx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + Endpoint: models.PLATFORM_APP, + }, + bidRequest: openrtb2.BidRequest{ + App: nil, + }, + }, + setup: func() { + mockCache.EXPECT().GetPartnerConfigMap(5890, 123, 1, models.PLATFORM_APP).Return( + nil, fmt.Errorf("error GetPartnerConfigMap")) + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + m := OpenWrap{ + cfg: tt.fields.cfg, + cache: tt.fields.cache, + metricEngine: tt.fields.metricEngine, + } + got, err := m.getProfileData(tt.args.rCtx, tt.args.bidRequest) + if (err != nil) != tt.wantErr { + assert.Equal(t, tt.wantErr, err != nil) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetTestModePartnerConfigMap(t *testing.T) { + type args struct { + platform string + timeout int64 + displayVersion int + } + tests := []struct { + name string + args args + want map[int]map[string]string + }{ + { + name: "get_test_mode_partnerConfigMap", + args: args{ + platform: "in-app", + timeout: 200, + displayVersion: 2, + }, + want: map[int]map[string]string{ + 1: { + models.PARTNER_ID: "1", + 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.PLATFORM_KEY: "in-app", + models.DisplayVersionID: "2", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getTestModePartnerConfigMap(tt.args.platform, tt.args.timeout, tt.args.displayVersion) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/schain.go b/modules/pubmatic/openwrap/schain.go new file mode 100644 index 00000000000..4440452ac46 --- /dev/null +++ b/modules/pubmatic/openwrap/schain.go @@ -0,0 +1,30 @@ +package openwrap + +import ( + "github.com/buger/jsonparser" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +func getSChainObj(partnerConfigMap map[int]map[string]string) []byte { + if partnerConfigMap != nil && partnerConfigMap[models.VersionLevelConfigID] != nil { + if partnerConfigMap[models.VersionLevelConfigID][models.SChainDBKey] == "1" { + sChainObjJSON := partnerConfigMap[models.VersionLevelConfigID][models.SChainObjectDBKey] + v, _, _, _ := jsonparser.Get([]byte(sChainObjJSON), "config") + return v + } + } + return nil +} + +// setSchainInSourceObject sets schain object in source.ext.schain +func setSchainInSourceObject(source *openrtb2.Source, schain []byte) { + if source.Ext == nil { + source.Ext = []byte("{}") + } + + sourceExt, err := jsonparser.Set(source.Ext, schain, models.SChainKey) + if err == nil { + source.Ext = sourceExt + } +} diff --git a/modules/pubmatic/openwrap/targeting.go b/modules/pubmatic/openwrap/targeting.go new file mode 100644 index 00000000000..9522dd6acdf --- /dev/null +++ b/modules/pubmatic/openwrap/targeting.go @@ -0,0 +1,121 @@ +package openwrap + +import ( + "fmt" + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/fullscreenclickability" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/utils" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// whitelist of prebid targeting keys +var prebidTargetingKeysWhitelist = map[string]struct{}{ + string(openrtb_ext.HbpbConstantKey): {}, + models.HbBuyIdPubmaticConstantKey: {}, + // OTT - 18 Deal priortization support + // this key required to send deal prefix and priority + string(openrtb_ext.HbCategoryDurationKey): {}, +} + +// check if prebid targeting keys are whitelisted +func allowTargetingKey(key string) bool { + if _, ok := prebidTargetingKeysWhitelist[key]; ok { + return true + } + return strings.HasPrefix(key, models.HbBuyIdPrefix) +} + +func addInAppTargettingKeys(targeting map[string]string, seat string, ecpm float64, bid *openrtb2.Bid, isWinningBid bool) { + targeting[models.CreatePartnerKey(seat, models.PWT_SLOTID)] = utils.GetOriginalBidId(bid.ID) + targeting[models.CreatePartnerKey(seat, models.PWT_SZ)] = models.GetSize(bid.W, bid.H) + targeting[models.CreatePartnerKey(seat, models.PWT_PARTNERID)] = seat + targeting[models.CreatePartnerKey(seat, models.PWT_ECPM)] = fmt.Sprintf("%.2f", ecpm) + targeting[models.CreatePartnerKey(seat, models.PWT_PLATFORM)] = getPlatformName(models.PLATFORM_APP) + targeting[models.CreatePartnerKey(seat, models.PWT_BIDSTATUS)] = "1" + if len(bid.DealID) != 0 { + targeting[models.CreatePartnerKey(seat, models.PWT_DEALID)] = bid.DealID + } + + if isWinningBid { + targeting[models.PWT_SLOTID] = utils.GetOriginalBidId(bid.ID) + targeting[models.PWT_BIDSTATUS] = "1" + targeting[models.PWT_SZ] = models.GetSize(bid.W, bid.H) + targeting[models.PWT_PARTNERID] = seat + targeting[models.PWT_ECPM] = fmt.Sprintf("%.2f", ecpm) + targeting[models.PWT_PLATFORM] = getPlatformName(models.PLATFORM_APP) + if len(bid.DealID) != 0 { + targeting[models.PWT_DEALID] = bid.DealID + } + } +} + +func addPWTTargetingForBid(rctx models.RequestCtx, bidResponse *openrtb2.BidResponse) (droppedBids map[string][]openrtb2.Bid, warnings []string) { + if !rctx.SendAllBids { + droppedBids = make(map[string][]openrtb2.Bid) + } + + //setTargeting needs a seperate loop as final winner would be decided after all the bids are processed by auction + for _, seatBid := range bidResponse.SeatBid { + for _, bid := range seatBid.Bid { + impCtx, ok := rctx.ImpBidCtx[bid.ImpID] + if !ok { + continue + } + + isWinningBid := false + if b, ok := rctx.WinningBids[bid.ImpID]; ok && b.ID == bid.ID { + isWinningBid = true + } + + if !(isWinningBid || rctx.SendAllBids) { + droppedBids[seatBid.Seat] = append(droppedBids[seatBid.Seat], bid) + } + + bidCtx, ok := impCtx.BidCtx[bid.ID] + if !ok { + continue + } + if bidCtx.Prebid == nil { + bidCtx.Prebid = new(openrtb_ext.ExtBidPrebid) + } + newTargeting := make(map[string]string) + for key, value := range bidCtx.Prebid.Targeting { + if allowTargetingKey(key) { + updatedKey := key + if strings.HasPrefix(key, models.PrebidTargetingKeyPrefix) { + updatedKey = strings.Replace(key, models.PrebidTargetingKeyPrefix, models.OWTargetingKeyPrefix, 1) + } + newTargeting[updatedKey] = value + } + delete(bidCtx.Prebid.Targeting, key) + } + + if rctx.Platform == models.PLATFORM_APP { + addInAppTargettingKeys(newTargeting, seatBid.Seat, bidCtx.NetECPM, &bid, isWinningBid) + } + bidCtx.Prebid.Targeting = newTargeting + + if isWinningBid { + if rctx.SendAllBids { + bidCtx.Winner = 1 + } + if fullscreenclickability.IsFscApplicable(rctx.PubID, seatBid.Seat, bidCtx.DspId) { + bidCtx.Fsc = 1 + } + } else if !rctx.SendAllBids { + warnings = append(warnings, "dropping bid "+utils.GetOriginalBidId(bid.ID)+" as sendAllBids is disabled") + } + + // cache for bid details for logger and tracker + if impCtx.BidCtx == nil { + impCtx.BidCtx = make(map[string]models.BidCtx) + } + impCtx.BidCtx[bid.ID] = bidCtx + rctx.ImpBidCtx[bid.ImpID] = impCtx + } + } + return +} diff --git a/modules/pubmatic/openwrap/tbf/tbf.go b/modules/pubmatic/openwrap/tbf/tbf.go new file mode 100644 index 00000000000..385de4f8df6 --- /dev/null +++ b/modules/pubmatic/openwrap/tbf/tbf.go @@ -0,0 +1,133 @@ +// Package tbf provides functionalities related to the Tracking-Beacon-First (TBF) feature. +// The package manages the configuration of the TBF feature, which includes publisher-profile-level +// traffic data, caching, and service reloader functionality. +package tbf + +import ( + "math/rand" + "sync" + "time" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" +) + +// tbf structure holds the configuration of Tracking-Beacon-First feature +type tbf struct { + pubProfileTraffic map[int]map[int]int + + cache cache.Cache + *sync.RWMutex + serviceStop chan (struct{}) +} + +var tbfConfigs tbf + +// initiateTBFReloader periodically update the TBF configuration from database +var initiateTBFReloader = func(c cache.Cache, expiryTime int) { + glog.Info("TBF Reloader start") + ticker := time.NewTicker(time.Duration(expiryTime) * time.Second) + + for { + updateTBFConfigMapsFromCache() + select { + case <-tbfConfigs.serviceStop: + return + case t := <-ticker.C: + glog.Infof("TBF Reloader loads cache @%v", t) + } + } +} + +// Init function initializes parameters of the tbfConfigs +// It starts the TBF reloader service in background +func Init(defaultExpiry int, cache cache.Cache) { + + tbfConfigs.cache = cache + tbfConfigs.pubProfileTraffic = make(map[int]map[int]int) + tbfConfigs.serviceStop = make(chan struct{}) + tbfConfigs.RWMutex = &sync.RWMutex{} + + go initiateTBFReloader(cache, defaultExpiry) + glog.Info("Initialized TBF cache reloaders to update publishers TBF configurations") +} + +// StopTBFReloaderService sends signal to stop the reloader service +func StopTBFReloaderService() { + tbfConfigs.serviceStop <- struct{}{} +} + +// limitTBFTrafficValues validates the traffic values from the given map of pub-prof-traffic +// to ensure they are constrained between 0 and 100 (inclusive). +// If a value is below 0 or above 100, it is set to 0. The original map is modified in place. +func limitTBFTrafficValues(pubProfTraffic map[int]map[int]int) { + for _, profTraffic := range pubProfTraffic { + for profID, traffic := range profTraffic { + if traffic < 0 || traffic > 100 { + profTraffic[profID] = 0 + } + } + } +} + +// updateTBFConfigMapsFromCache loads the TBF traffic data from cache/database and updates the configuration map. +// If execution of db-query-fails then this function will not update the old config-values. +// This function is safe for concurrent access. +func updateTBFConfigMapsFromCache() error { + + pubProfileTrafficRate, err := tbfConfigs.cache.GetTBFTrafficForPublishers() + if err != nil { + return err + } + limitTBFTrafficValues(pubProfileTrafficRate) + + tbfConfigs.Lock() + tbfConfigs.pubProfileTraffic = pubProfileTrafficRate + tbfConfigs.Unlock() + + return nil +} + +// IsEnabledTBFFeature returns false if TBF feature is disabled for pub-profile combination +// It makes use of predictTBFValue function to predict whether the request is eligible +// to track beacon first before adm based on the provided traffic percentage. +// This function is safe for concurrent access. +func IsEnabledTBFFeature(pubid int, profid int) bool { + + var trafficRate int + var present bool + + tbfConfigs.RLock() + if tbfConfigs.pubProfileTraffic != nil { + trafficRate, present = tbfConfigs.pubProfileTraffic[pubid][profid] + } + tbfConfigs.RUnlock() + + if !present { + return false + } + + return predictTBFValue(trafficRate) +} + +// predictTBFValue predicts whether a request is eligible for TBF feature +// based on the provided trafficRate value. +func predictTBFValue(trafficRate int) bool { + return rand.Intn(100) < trafficRate +} + +// SetAndResetTBFConfig is exposed for test cases +func SetAndResetTBFConfig(mockDb cache.Cache, pubProfileTraffic map[int]map[int]int) func() { + tbfConfigs.RWMutex = &sync.RWMutex{} + tbfConfigs.cache = mockDb + tbfConfigs.pubProfileTraffic = pubProfileTraffic + return func() { + tbfConfigs.cache = nil + tbfConfigs.pubProfileTraffic = make(map[int]map[int]int) + } +} + +// ResetTBFReloader is exposed for test cases +func ResetTBFReloader() { + initiateTBFReloader = func(c cache.Cache, expiryTime int) {} +} diff --git a/modules/pubmatic/openwrap/tbf/tbf_test.go b/modules/pubmatic/openwrap/tbf/tbf_test.go new file mode 100644 index 00000000000..c4c32935eb7 --- /dev/null +++ b/modules/pubmatic/openwrap/tbf/tbf_test.go @@ -0,0 +1,233 @@ +package tbf + +import ( + "fmt" + "testing" + "time" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + mock_cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache/mock" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestInitAndReloader(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockCache := mock_cache.NewMockCache(ctrl) + defer SetAndResetTBFConfig(mockCache, nil)() + + type args struct { + defaultExpiry int + cache cache.Cache + } + + tests := []struct { + name string + args args + runBefore func() + }{ + { + name: "test_cache_call_through_init", + args: args{ + defaultExpiry: 1, + cache: mockCache, + }, + runBefore: func() { + mockCache.EXPECT().GetTBFTrafficForPublishers().Return(map[int]map[int]int{}, nil).AnyTimes() + }, + }, + } + for _, tt := range tests { + tt.runBefore() + tbfConfigs.serviceStop = make(chan struct{}) + Init(tt.args.defaultExpiry, tt.args.cache) + time.Sleep(2 * time.Second) + StopTBFReloaderService() + time.Sleep(250 * time.Millisecond) + } +} + +func TestPredictTBFValue(t *testing.T) { + type args struct { + percentage int + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "100_pct_traffic", + args: args{ + percentage: 100, + }, + want: true, + }, + { + name: "0_pct_traffic", + args: args{ + percentage: 0, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := predictTBFValue(tt.args.percentage) + assert.Equal(t, tt.want, got, tt.name) + }) + } +} + +func TestIsEnabledTBFFeature(t *testing.T) { + + type args struct { + pubidstr int + profid int + pubProfTraffic map[int]map[int]int + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "nil_map", + args: args{ + pubidstr: 5890, + profid: 1234, + pubProfTraffic: nil, + }, + want: false, + }, + { + name: "pub_prof_absent_in_map", + args: args{ + pubidstr: 5890, + profid: 1234, + pubProfTraffic: make(map[int]map[int]int), + }, + want: false, + }, + { + name: "pub_prof_present_in_map", + args: args{ + pubidstr: 5890, + profid: 1234, + pubProfTraffic: map[int]map[int]int{ + 5890: {1234: 100}, + }, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + SetAndResetTBFConfig(nil, tt.args.pubProfTraffic) + got := IsEnabledTBFFeature(tt.args.pubidstr, tt.args.profid) + assert.Equal(t, tt.want, got, tt.name) + }) + } +} + +func TestUpdateTBFConfigMapsFromCache(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockCache := mock_cache.NewMockCache(ctrl) + + defer SetAndResetTBFConfig(mockCache, map[int]map[int]int{})() + type want struct { + err error + pubProfileTraffic map[int]map[int]int + } + + tests := []struct { + name string + setup func() + want want + }{ + { + name: "cache_returns_error", + setup: func() { + mockCache.EXPECT().GetTBFTrafficForPublishers().Return(nil, fmt.Errorf("error")) + }, + want: want{ + pubProfileTraffic: map[int]map[int]int{}, + err: fmt.Errorf("error"), + }, + }, + { + name: "cache_returns_success", + setup: func() { + mockCache.EXPECT().GetTBFTrafficForPublishers().Return(map[int]map[int]int{5890: {1234: 100}}, nil) + }, + want: want{ + pubProfileTraffic: map[int]map[int]int{5890: {1234: 100}}, + err: nil, + }, + }, + { + name: "limit_traffic_values", + setup: func() { + mockCache.EXPECT().GetTBFTrafficForPublishers().Return(map[int]map[int]int{5890: {1234: 200}, 5891: {222: -5}}, nil) + }, + want: want{ + pubProfileTraffic: map[int]map[int]int{5890: {1234: 0}, 5891: {222: 0}}, + err: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setup() + err := updateTBFConfigMapsFromCache() + assert.Equal(t, tt.want.err, err, tt.name) + assert.Equal(t, tt.want.pubProfileTraffic, tbfConfigs.pubProfileTraffic, tt.name) + }) + } +} + +func TestLimitTBFTrafficValues(t *testing.T) { + + tests := []struct { + name string + inputMap map[int]map[int]int + outputMap map[int]map[int]int + }{ + { + name: "nil_map", + inputMap: nil, + outputMap: nil, + }, + { + name: "nil_prof_traffic_map", + inputMap: map[int]map[int]int{ + 1: nil, + }, + outputMap: map[int]map[int]int{ + 1: nil, + }, + }, + { + name: "negative_and_higher_than_100_values", + inputMap: map[int]map[int]int{ + 5890: {123: -100}, + 5891: {123: 50}, + 5892: {123: 200}, + }, + outputMap: map[int]map[int]int{ + 5890: {123: 0}, + 5891: {123: 50}, + 5892: {123: 0}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + limitTBFTrafficValues(tt.inputMap) + assert.Equal(t, tt.outputMap, tt.inputMap, tt.name) + }) + } +} diff --git a/modules/pubmatic/openwrap/tracker/banner.go b/modules/pubmatic/openwrap/tracker/banner.go new file mode 100644 index 00000000000..96de6aca8fd --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/banner.go @@ -0,0 +1,59 @@ +package tracker + +import ( + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/tbf" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func injectBannerTracker(rctx models.RequestCtx, tracker models.OWTracker, bid openrtb2.Bid, seat string, pixels []adunitconfig.UniversalPixel) string { + var replacedTrackerStr, trackerFormat string + trackerFormat = models.TrackerCallWrap + if trackerWithOM(tracker, rctx.Platform, seat) { + trackerFormat = models.TrackerCallWrapOMActive + } + replacedTrackerStr = strings.Replace(trackerFormat, "${escapedUrl}", tracker.TrackerURL, 1) + adm := applyTBFFeature(rctx, bid, replacedTrackerStr) + return appendUPixelinBanner(adm, pixels) +} + +// append universal pixels in creative based on conditions +func appendUPixelinBanner(adm string, universalPixel []adunitconfig.UniversalPixel) string { + if universalPixel == nil { + return adm + } + + for _, pixelVal := range universalPixel { + if pixelVal.Pos == models.PixelPosAbove { + adm = pixelVal.Pixel + adm + continue + } + adm = adm + pixelVal.Pixel + } + return adm +} + +// TrackerWithOM checks for OM active condition for DV360 +func trackerWithOM(tracker models.OWTracker, platform, bidderCode string) bool { + if platform == models.PLATFORM_APP && bidderCode == string(openrtb_ext.BidderPubmatic) { + if tracker.DspId == models.DspId_DV360 { + return true + } + } + return false +} + +// applyTBFFeature adds the tracker before or after the actual bid.Adm +// If TBF feature is applicable based on database-configuration for +// given pub-prof combination then injects the tracker before adm +// else injects the tracker after adm. +func applyTBFFeature(rctx models.RequestCtx, bid openrtb2.Bid, tracker string) string { + if tbf.IsEnabledTBFFeature(rctx.PubID, rctx.ProfileID) { + return tracker + bid.AdM + } + return bid.AdM + tracker +} diff --git a/modules/pubmatic/openwrap/tracker/banner_test.go b/modules/pubmatic/openwrap/tracker/banner_test.go new file mode 100644 index 00000000000..310dff05897 --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/banner_test.go @@ -0,0 +1,301 @@ +package tracker + +import ( + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + mock_cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/tbf" + "github.com/stretchr/testify/assert" +) + +func Test_injectBannerTracker(t *testing.T) { + tbf.SetAndResetTBFConfig(&mock_cache.MockCache{}, map[int]map[int]int{ + 5890: {1234: 100}, + }) + type args struct { + rctx models.RequestCtx + tracker models.OWTracker + bid openrtb2.Bid + seat string + pixels []adunitconfig.UniversalPixel + } + tests := []struct { + name string + args args + want string + }{ + { + name: "app_platform", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + }, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + bid: openrtb2.Bid{ + AdM: `sample_creative`, + }, + seat: "test", + }, + want: `sample_creative
`, + }, + { + name: "app_platform_OM_Inactive_pubmatic", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + }, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + DspId: -1, + }, + bid: openrtb2.Bid{ + AdM: `sample_creative`, + }, + seat: models.BidderPubMatic, + }, + want: `sample_creative
`, + }, + { + name: "app_platform_OM_Active_pubmatic", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + }, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + DspId: models.DspId_DV360, + }, + bid: openrtb2.Bid{ + AdM: `sample_creative`, + }, + seat: models.BidderPubMatic, + }, + want: `sample_creative`, + }, + { + name: "tbf_feature_enabled", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 1234, + }, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + bid: openrtb2.Bid{ + AdM: `sample_creative`, + }, + }, + want: `
sample_creative`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := injectBannerTracker(tt.args.rctx, tt.args.tracker, tt.args.bid, tt.args.seat, tt.args.pixels); got != tt.want { + t.Errorf("injectBannerTracker() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_trackerWithOM(t *testing.T) { + type args struct { + tracker models.OWTracker + platform string + bidderCode string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "in-app_partner_otherthan_pubmatic", + args: args{ + tracker: models.OWTracker{ + DspId: models.DspId_DV360, + }, + platform: models.PLATFORM_APP, + bidderCode: "test", + }, + want: false, + }, + { + name: "in-app_partner_pubmatic_other_dv360", + args: args{ + tracker: models.OWTracker{ + DspId: -1, + }, + platform: models.PLATFORM_APP, + bidderCode: models.BidderPubMatic, + }, + want: false, + }, + { + name: "display_partner_pubmatic_dv360", + args: args{ + tracker: models.OWTracker{ + DspId: models.DspId_DV360, + }, + platform: models.PLATFORM_DISPLAY, + bidderCode: models.BidderPubMatic, + }, + want: false, + }, + { + name: "in-app_partner_pubmatic_dv360", + args: args{ + tracker: models.OWTracker{ + DspId: models.DspId_DV360, + }, + platform: models.PLATFORM_APP, + bidderCode: models.BidderPubMatic, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := trackerWithOM(tt.args.tracker, tt.args.platform, tt.args.bidderCode); got != tt.want { + t.Errorf("trackerWithOM() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_applyTBFFeature(t *testing.T) { + tbf.SetAndResetTBFConfig(&mock_cache.MockCache{}, map[int]map[int]int{ + 5890: {1234: 100}, + }) + + type args struct { + rctx models.RequestCtx + bid openrtb2.Bid + tracker string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "tbf_feature_disabled", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 100, + }, + bid: openrtb2.Bid{ + AdM: "bid_AdM", + }, + tracker: "tracker_url", + }, + want: "bid_AdMtracker_url", + }, + { + name: "tbf_feature_enabled", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 1234, + }, + bid: openrtb2.Bid{ + AdM: "bid_AdM", + }, + tracker: "tracker_url", + }, + want: "tracker_urlbid_AdM", + }, + { + name: "invalid_pubid", + args: args{ + rctx: models.RequestCtx{ + PubID: -1, + ProfileID: 1234, + }, + bid: openrtb2.Bid{ + AdM: "bid_AdM", + }, + tracker: "tracker_url", + }, + want: "bid_AdMtracker_url", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := applyTBFFeature(tt.args.rctx, tt.args.bid, tt.args.tracker); got != tt.want { + t.Errorf("applyTBFFeature() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_appendUPixelinBanner(t *testing.T) { + type args struct { + adm string + universalPixel []adunitconfig.UniversalPixel + } + type want struct { + creative string + } + tests := []struct { + name string + args args + want want + }{ + { + name: "empty universal pixel", + args: args{ + adm: `sample_creative`, + }, + want: want{ + creative: `sample_creative`, + }, + }, + { + name: "valid insertion of upixel", + args: args{ + adm: `sample_creative`, + universalPixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: `
`, + PixelType: models.PixelTypeUrl, + Pos: models.PixelPosAbove, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + { + Id: 123, + Pixel: "", + PixelType: models.PixelTypeJS, + Pos: models.PixelPosBelow, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + { + Id: 123, + Pixel: `
`, + PixelType: "url", + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + }, + }, + want: want{ + creative: `
sample_creative
`, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := appendUPixelinBanner(tt.args.adm, tt.args.universalPixel) + assert.Equal(t, tt.want.creative, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/tracker/create.go b/modules/pubmatic/openwrap/tracker/create.go new file mode 100644 index 00000000000..9ca2aabd5ae --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/create.go @@ -0,0 +1,334 @@ +package tracker + +import ( + "bytes" + "fmt" + "net/url" + "strconv" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/utils" +) + +// pubmatic's KGP details per impression +type pubmaticMarketplaceMeta struct { + PubmaticKGP, PubmaticKGPV, PubmaticKGPSV string +} + +func CreateTrackers(rctx models.RequestCtx, bidResponse *openrtb2.BidResponse) map[string]models.OWTracker { + trackers := make(map[string]models.OWTracker) + + pmMkt := make(map[string]pubmaticMarketplaceMeta) + + trackers = createTrackers(rctx, trackers, bidResponse, pmMkt) + + // overwrite marketplace bid details with that of parent bidder + for bidID, tracker := range trackers { + if _, ok := rctx.MarketPlaceBidders[tracker.Tracker.PartnerInfo.BidderCode]; ok { + if v, ok := pmMkt[tracker.Tracker.ImpID]; ok { + tracker.Tracker.PartnerInfo.PartnerID = "pubmatic" + tracker.Tracker.PartnerInfo.KGPV = v.PubmaticKGPV + tracker.Tracker.LoggerData.KGPSV = v.PubmaticKGPSV + } + } + + var finalTrackerURL string + trackerURL := constructTrackerURL(rctx, tracker.Tracker) + trackURL, err := url.Parse(trackerURL) + if err == nil { + trackURL.Scheme = models.HTTPSProtocol + finalTrackerURL = trackURL.String() + } + tracker.TrackerURL = finalTrackerURL + + trackers[bidID] = tracker + } + + return trackers +} + +func createTrackers(rctx models.RequestCtx, trackers map[string]models.OWTracker, bidResponse *openrtb2.BidResponse, pmMkt map[string]pubmaticMarketplaceMeta) map[string]models.OWTracker { + floorsDetails := models.GetFloorsDetails(rctx.ResponseExt) + for _, seatBid := range bidResponse.SeatBid { + for _, bid := range seatBid.Bid { + tracker := models.Tracker{ + PubID: rctx.PubID, + ProfileID: fmt.Sprintf("%d", rctx.ProfileID), + VersionID: fmt.Sprintf("%d", rctx.DisplayVersionID), + PageURL: rctx.PageURL, + Timestamp: rctx.StartTime, + IID: rctx.LoggerImpressionID, + Platform: int(rctx.DevicePlatform), + SSAI: rctx.SSAI, + ImpID: bid.ImpID, + Origin: rctx.Origin, + AdPodSlot: 0, //TODO: Need to changes based on AdPodSlot Obj for CTV Req + TestGroup: rctx.ABTestConfigApplied, + FloorModelVersion: floorsDetails.FloorModelVersion, + FloorType: floorsDetails.FloorType, + FloorSkippedFlag: floorsDetails.Skipfloors, + FloorSource: floorsDetails.FloorSource, + LoggerData: models.LoggerData{ + FloorFetchStatus: floorsDetails.FloorFetchStatus, + FloorProvider: floorsDetails.FloorProvider, + }, + } + var ( + kgp, kgpv, kgpsv, matchedSlot, adformat, bidId = "", "", "", "", "banner", "" + floorValue, floorRuleValue = float64(0), float64(0) + partnerID = seatBid.Seat + isRewardInventory, adduration = 0, 0 + dspId int + eg, en float64 + ) + + if impCtx, ok := rctx.ImpBidCtx[bid.ImpID]; ok { + if bidderMeta, ok := impCtx.Bidders[seatBid.Seat]; ok { + matchedSlot = bidderMeta.MatchedSlot + partnerID = bidderMeta.PrebidBidderCode + } + + bidCtx, ok := impCtx.BidCtx[bid.ID] + if ok { + // TODO do most calculation in wt + // marketplace/alternatebiddercodes feature + bidExt := bidCtx.BidExt + if bidExt.Prebid != nil { + if bidExt.Prebid.Video != nil && bidExt.Prebid.Video.Duration > 0 { + adduration = bidExt.Prebid.Video.Duration + } + if len(bidExt.Prebid.BidId) > 0 { + bidId = bidExt.Prebid.BidId + } + if bidExt.Prebid.Meta != nil && len(bidExt.Prebid.Meta.AdapterCode) != 0 && seatBid.Seat != bidExt.Prebid.Meta.AdapterCode { + partnerID = bidExt.Prebid.Meta.AdapterCode + + if aliasSeat, ok := rctx.PrebidBidderCode[partnerID]; ok { + if bidderMeta, ok := impCtx.Bidders[aliasSeat]; ok { + matchedSlot = bidderMeta.MatchedSlot + } + } + } + } + dspId = bidCtx.DspId + eg = bidCtx.EG + en = bidCtx.EN + adformat = models.GetAdFormat(&bid, &bidExt, &impCtx) + floorValue, floorRuleValue = models.GetBidLevelFloorsDetails(bidExt, impCtx, rctx.CurrencyConversion) + } + + _ = matchedSlot + // -------------------------------------------------------------------------------------------------- + // Move this code to a function. Confirm the kgp, kgpv, kgpsv relation in wt and wl. + // -------------------------------------------------------------------------------------------------- + // var kgp, kgpv, kgpsv string + + if bidderMeta, ok := impCtx.Bidders[seatBid.Seat]; ok { + partnerID = bidderMeta.PrebidBidderCode + kgp = bidderMeta.KGP + kgpv, kgpsv = models.GetKGPSV(bid, bidderMeta, adformat, impCtx.TagID, impCtx.Div, rctx.Source) + } + // -------------------------------------------------------------------------------------------------- + + tracker.SlotID = impCtx.SlotName + tracker.LoggerData.KGPSV = kgpsv + tracker.Secure = impCtx.Secure + tracker.Adunit = impCtx.AdUnitName + isRewardInventory = 0 + if impCtx.IsRewardInventory != nil { + isRewardInventory = int(*impCtx.IsRewardInventory) + } + } + + if seatBid.Seat == "pubmatic" { + pmMkt[bid.ImpID] = pubmaticMarketplaceMeta{ + PubmaticKGP: kgp, + PubmaticKGPV: kgpv, + PubmaticKGPSV: kgpsv, + } + } + + tracker.RewardedInventory = isRewardInventory + tracker.PartnerInfo = models.Partner{ + PartnerID: partnerID, + BidderCode: seatBid.Seat, + BidID: utils.GetOriginalBidId(bid.ID), + OrigBidID: utils.GetOriginalBidId(bid.ID), + KGPV: kgpv, + NetECPM: en, + GrossECPM: eg, + AdSize: models.GetSizeForPlatform(bid.W, bid.H, rctx.Platform), + AdDuration: adduration, + Adformat: adformat, + ServerSide: 1, + FloorValue: floorValue, + FloorRuleValue: floorRuleValue, + DealID: "-1", + } + if len(bidId) > 0 { + tracker.PartnerInfo.BidID = bidId + } + if len(bid.ADomain) != 0 { + if domain, err := models.ExtractDomain(bid.ADomain[0]); err == nil { + tracker.PartnerInfo.Advertiser = domain + } + } + if len(bid.DealID) > 0 { + tracker.PartnerInfo.DealID = bid.DealID + } + + var finalTrackerURL string + trackerURL := constructTrackerURL(rctx, tracker) + trackURL, err := url.Parse(trackerURL) + if err == nil { + trackURL.Scheme = models.HTTPSProtocol + finalTrackerURL = trackURL.String() + } + + trackers[bid.ID] = models.OWTracker{ + Tracker: tracker, + TrackerURL: finalTrackerURL, + Price: bid.Price, + PriceModel: models.VideoPricingModelCPM, + PriceCurrency: bidResponse.Cur, + ErrorURL: constructVideoErrorURL(rctx, rctx.VideoErrorTrackerEndpoint, bid, tracker), + BidType: adformat, + DspId: dspId, + } + } + } + return trackers +} + +// ConstructTrackerURL constructing tracker url for impression +func constructTrackerURL(rctx models.RequestCtx, tracker models.Tracker) string { + trackerURL, err := url.Parse(rctx.TrackerEndpoint) + if err != nil { + return "" + } + + v := url.Values{} + v.Set(models.TRKPubID, strconv.Itoa(tracker.PubID)) + v.Set(models.TRKPageURL, tracker.PageURL) + v.Set(models.TRKTimestamp, strconv.FormatInt(tracker.Timestamp, 10)) + v.Set(models.TRKIID, tracker.IID) + v.Set(models.TRKProfileID, tracker.ProfileID) + v.Set(models.TRKVersionID, tracker.VersionID) + v.Set(models.TRKSlotID, tracker.SlotID) + v.Set(models.TRKAdunit, tracker.Adunit) + if tracker.RewardedInventory == 1 { + v.Set(models.TRKRewardedInventory, strconv.Itoa(tracker.RewardedInventory)) + } + v.Set(models.TRKPlatform, strconv.Itoa(tracker.Platform)) + v.Set(models.TRKTestGroup, strconv.Itoa(tracker.TestGroup)) + v.Set(models.TRKPubDomain, tracker.Origin) + v.Set(models.TRKAdPodExist, strconv.Itoa(tracker.AdPodSlot)) + partner := tracker.PartnerInfo + v.Set(models.TRKPartnerID, partner.PartnerID) + v.Set(models.TRKBidderCode, partner.BidderCode) + v.Set(models.TRKKGPV, partner.KGPV) + v.Set(models.TRKGrossECPM, fmt.Sprint(partner.GrossECPM)) + v.Set(models.TRKNetECPM, fmt.Sprint(partner.NetECPM)) + v.Set(models.TRKBidID, partner.BidID) + if tracker.SSAI != "" { + v.Set(models.TRKSSAI, tracker.SSAI) + } + v.Set(models.TRKOrigBidID, partner.OrigBidID) + v.Set(models.TRKAdSize, partner.AdSize) + if partner.AdDuration > 0 { + v.Set(models.TRKAdDuration, strconv.Itoa(partner.AdDuration)) + } + v.Set(models.TRKAdformat, partner.Adformat) + v.Set(models.TRKServerSide, strconv.Itoa(partner.ServerSide)) + v.Set(models.TRKAdvertiser, partner.Advertiser) + + v.Set(models.TRKFloorType, strconv.Itoa(tracker.FloorType)) + if tracker.FloorSkippedFlag != nil { + v.Set(models.TRKFloorSkippedFlag, strconv.Itoa(*tracker.FloorSkippedFlag)) + } + if len(tracker.FloorModelVersion) > 0 { + v.Set(models.TRKFloorModelVersion, tracker.FloorModelVersion) + } + if tracker.FloorSource != nil { + v.Set(models.TRKFloorSource, strconv.Itoa(*tracker.FloorSource)) + } + if partner.FloorValue > 0 { + v.Set(models.TRKFloorValue, fmt.Sprint(partner.FloorValue)) + } + if partner.FloorRuleValue > 0 { + v.Set(models.TRKFloorRuleValue, fmt.Sprint(partner.FloorRuleValue)) + } + v.Set(models.TRKServerLogger, "1") + v.Set(models.TRKDealID, partner.DealID) + queryString := v.Encode() + + //Code for making tracker call http/https based on secure flag for in-app platform + //TODO change platform to models.PLATFORM_APP once in-app platform starts populating from wrapper UI + if rctx.Platform == models.PLATFORM_DISPLAY { + if tracker.Secure == 1 { + trackerURL.Scheme = "https" + } else { + trackerURL.Scheme = "http" + } + + } + trackerQueryStr := trackerURL.String() + models.TRKQMARK + queryString + return trackerQueryStr +} + +// ConstructVideoErrorURL constructing video error url for video impressions +func constructVideoErrorURL(rctx models.RequestCtx, errorURLString string, bid openrtb2.Bid, tracker models.Tracker) string { + if len(errorURLString) == 0 { + return "" + } + + errorURL, err := url.Parse(errorURLString) + if err != nil { + return "" + } + + errorURL.Scheme = models.HTTPSProtocol + tracker.SURL = url.QueryEscape(rctx.Origin) + + //operId Note: It should be first parameter in url otherwise it will get failed at analytics side. + if len(errorURL.RawQuery) > 0 { + errorURL.RawQuery = models.ERROperIDParam + models.TRKAmpersand + errorURL.RawQuery + } else { + errorURL.RawQuery = models.ERROperIDParam + } + + v := url.Values{} + v.Set(models.ERRPubID, strconv.Itoa(tracker.PubID)) //pubId + v.Set(models.ERRProfileID, tracker.ProfileID) //profileId + v.Set(models.ERRVersionID, tracker.VersionID) //versionId + v.Set(models.ERRTimestamp, strconv.FormatInt(tracker.Timestamp, 10)) //ts + v.Set(models.ERRPartnerID, tracker.PartnerInfo.PartnerID) //pid + v.Set(models.ERRBidderCode, tracker.PartnerInfo.BidderCode) //bc + v.Set(models.ERRAdunit, tracker.Adunit) //au + v.Set(models.ERRSUrl, tracker.SURL) // sURL + v.Set(models.ERRPlatform, strconv.Itoa(tracker.Platform)) // pfi + v.Set(models.ERRAdvertiser, tracker.PartnerInfo.Advertiser) // adv + + if tracker.SSAI != "" { + v.Set(models.ERRSSAI, tracker.SSAI) // ssai for video/json endpoint + } + + if bid.CrID == "" { + v.Set(models.ERRCreativeID, "-1") + } else { + v.Set(models.ERRCreativeID, bid.CrID) //creativeId + } + + var out bytes.Buffer + out.WriteString(errorURL.String()) + out.WriteString(models.TRKAmpersand) + out.WriteString(v.Encode()) + out.WriteString(models.TRKAmpersand) + out.WriteString(models.ERRErrorCodeParam) //ier + + //queryString += + errorURLQueryStr := out.String() + + return errorURLQueryStr +} diff --git a/modules/pubmatic/openwrap/tracker/create_test.go b/modules/pubmatic/openwrap/tracker/create_test.go new file mode 100644 index 00000000000..ea25b4d94fc --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/create_test.go @@ -0,0 +1,846 @@ +package tracker + +import ( + "net/url" + "strconv" + "testing" + "time" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +var rctx = models.RequestCtx{ + PubID: 5890, + ProfileID: 1234, + DisplayID: 1, + DisplayVersionID: 1, + PageURL: "abc.com", + LoggerImpressionID: "loggerIID", + DevicePlatform: 5, + SSAI: "mediatailor", + Origin: "publisher.com", + ABTestConfigApplied: 1, + PrebidBidderCode: map[string]string{ + "pubmatic": "pubmatic", + }, + MarketPlaceBidders: map[string]struct{}{ + "pubmatic": {}, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "impID-1": { + TagID: "adunit-1", + AdUnitName: "adunit-1", + SlotName: "impID-1_adunit-1", + Bidders: map[string]models.PartnerData{ + "pubmatic": { + MatchedSlot: "matchedSlot", + PrebidBidderCode: "prebidBidderCode", + KGP: "_AU_@_W_x_H_", + }, + "pubmatic2": { + MatchedSlot: "matchedSlot2", + PrebidBidderCode: "prebidBidderCode2", + KGP: "_AU_@_W_x_H_", + }, + }, + BidFloor: 5.5, + BidFloorCur: "EUR", + BidCtx: map[string]models.BidCtx{ + "bidID-1": { + EG: 8.7, + EN: 8.7, + BidExt: models.BidExt{ + OriginalBidCPMUSD: 0, + ExtBid: openrtb_ext.ExtBid{ + Prebid: &openrtb_ext.ExtBidPrebid{ + BidId: "bidID-1", + Video: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 20, + }, + Meta: &openrtb_ext.ExtBidPrebidMeta{ + AdapterCode: "pubmatic", + }, + Floors: &openrtb_ext.ExtBidPrebidFloors{ + FloorRule: "rule1", + FloorValue: 6.4, + FloorRuleValue: 4.4, + }, + Type: models.Banner, + }, + }, + }, + }, + }, + }, + }, +} + +func Test_createTrackers(t *testing.T) { + startTime := time.Now().Unix() + type args struct { + trackers map[string]models.OWTracker + rctx models.RequestCtx + bidResponse *openrtb2.BidResponse + pmMkt map[string]pubmaticMarketplaceMeta + } + tests := []struct { + name string + args args + want map[string]models.OWTracker + }{ + { + name: "empty_bidResponse", + args: args{ + trackers: map[string]models.OWTracker{}, + bidResponse: &openrtb2.BidResponse{}, + }, + want: map[string]models.OWTracker{}, + }, + { + name: "response with all details", + args: args{ + trackers: map[string]models.OWTracker{}, + rctx: func() models.RequestCtx { + testRctx := rctx + testRctx.StartTime = startTime + return testRctx + }(), + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bidID-1", + ImpID: "impID-1", + Price: 8.7, + W: 250, + H: 300, + ADomain: []string{"domain.com"}, + DealID: "deal-id-1", + }, + }, + Seat: "pubmatic", + }, + }, + Cur: models.USD, + }, + pmMkt: map[string]pubmaticMarketplaceMeta{}, + }, + want: map[string]models.OWTracker{ + "bidID-1": { + Tracker: models.Tracker{ + PubID: 5890, + PageURL: "abc.com", + Timestamp: startTime, + IID: "loggerIID", + ProfileID: "1234", + VersionID: "1", + Adunit: "adunit-1", + SlotID: "impID-1_adunit-1", + PartnerInfo: models.Partner{ + PartnerID: "prebidBidderCode", + BidderCode: "pubmatic", + KGPV: "adunit-1@250x300", + GrossECPM: 8.7, + NetECPM: 8.7, + BidID: "bidID-1", + OrigBidID: "bidID-1", + AdSize: "250x300", + AdDuration: 20, + Adformat: "banner", + ServerSide: 1, + Advertiser: "domain.com", + FloorValue: 6.4, + FloorRuleValue: 4.4, + DealID: "deal-id-1", + }, + Platform: 5, + SSAI: "mediatailor", + AdPodSlot: 0, + TestGroup: 1, + Origin: "publisher.com", + ImpID: "impID-1", + LoggerData: models.LoggerData{ + KGPSV: "adunit-1@250x300", + }, + }, + TrackerURL: "https:?adv=domain.com&af=banner&aps=0&au=adunit-1&bc=pubmatic&bidid=bidID-1&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, + PriceModel: "CPM", + PriceCurrency: "USD", + BidType: "banner", + }, + }, + }, + { + name: "response with all details with alias partner", + args: args{ + trackers: map[string]models.OWTracker{}, + rctx: func() models.RequestCtx { + testRctx := rctx + testRctx.StartTime = startTime + testRctx.PrebidBidderCode["pubmatic"] = "pubmatic2" + return testRctx + }(), + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bidID-1", + ImpID: "impID-1", + Price: 8.7, + W: 250, + H: 300, + ADomain: []string{"domain.com"}, + DealID: "deal-id-1", + }, + }, + Seat: "pubmatic2", + }, + }, + Cur: models.USD, + }, + pmMkt: map[string]pubmaticMarketplaceMeta{}, + }, + want: map[string]models.OWTracker{ + "bidID-1": { + Tracker: models.Tracker{ + PubID: 5890, + PageURL: "abc.com", + Timestamp: startTime, + IID: "loggerIID", + ProfileID: "1234", + VersionID: "1", + Adunit: "adunit-1", + SlotID: "impID-1_adunit-1", + PartnerInfo: models.Partner{ + PartnerID: "prebidBidderCode2", + BidderCode: "pubmatic2", + KGPV: "adunit-1@250x300", + GrossECPM: 8.7, + NetECPM: 8.7, + BidID: "bidID-1", + OrigBidID: "bidID-1", + AdSize: "250x300", + AdDuration: 20, + Adformat: "banner", + ServerSide: 1, + Advertiser: "domain.com", + FloorValue: 6.4, + FloorRuleValue: 4.4, + DealID: "deal-id-1", + }, + Platform: 5, + SSAI: "mediatailor", + AdPodSlot: 0, + TestGroup: 1, + Origin: "publisher.com", + ImpID: "impID-1", + LoggerData: models.LoggerData{ + KGPSV: "adunit-1@250x300", + }, + }, + TrackerURL: "https:?adv=domain.com&af=banner&aps=0&au=adunit-1&bc=pubmatic2&bidid=bidID-1&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=prebidBidderCode2&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, + PriceModel: "CPM", + PriceCurrency: "USD", + BidType: "banner", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := createTrackers(tt.args.rctx, tt.args.trackers, tt.args.bidResponse, tt.args.pmMkt) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestConstructTrackerURL(t *testing.T) { + type args struct { + rctx models.RequestCtx + tracker models.Tracker + } + tests := []struct { + name string + args args + want string + }{ + { + name: "trackerEndpoint_parsingError", + args: args{ + rctx: models.RequestCtx{ + TrackerEndpoint: ":www.example.com", + }, + }, + want: "", + }, + { + name: "empty_tracker_details", + args: args{ + rctx: models.RequestCtx{ + TrackerEndpoint: "//t.pubmatic.com/wt", + Platform: models.PLATFORM_DISPLAY, + }, + tracker: models.Tracker{}, + }, + want: "http://t.pubmatic.com/wt?adv=&af=&aps=0&au=&bc=&bidid=&di=&eg=0&en=0&ft=0&iid=&kgpv=&orig=&origbidid=&pdvid=&pid=&plt=0&pn=&psz=&pubid=0&purl=&sl=1&slot=&ss=0&tgid=0&tst=0", + }, + { + name: "platform_amp_with_tracker_details", + args: args{ + rctx: models.RequestCtx{ + TrackerEndpoint: "//t.pubmatic.com/wt", + Platform: models.PLATFORM_AMP, + }, + tracker: models.Tracker{ + PubID: 12345, + PageURL: "www.abc.com", + IID: "98765", + ProfileID: "123", + VersionID: "1", + SlotID: "1234_1234", + Adunit: "adunit", + Platform: 1, + Origin: "www.publisher.com", + TestGroup: 1, + AdPodSlot: 0, + PartnerInfo: models.Partner{ + PartnerID: "AppNexus", + BidderCode: "AppNexus1", + BidID: "6521", + OrigBidID: "6521", + GrossECPM: 4.3, + NetECPM: 2.5, + KGPV: "adunit@300x250", + AdDuration: 10, + Adformat: models.Banner, + AdSize: "300x250", + ServerSide: 1, + Advertiser: "fb.com", + DealID: "420", + }, + }, + }, + want: "//t.pubmatic.com/wt?adv=fb.com&af=banner&aps=0&au=adunit&bc=AppNexus1&bidid=6521&di=420&dur=10&eg=4.3&en=2.5&ft=0&iid=98765&kgpv=adunit@300x250&orig=www.publisher.com&origbidid=6521&pdvid=1&pid=123&plt=1&pn=AppNexus&psz=300x250&pubid=12345&purl=www.abc.com&sl=1&slot=1234_1234&ss=1&tgid=1&tst=0", + }, + { + name: "all_details_with_ssai_in_tracker", + args: args{ + rctx: models.RequestCtx{ + TrackerEndpoint: "//t.pubmatic.com/wt", + Platform: models.PLATFORM_DISPLAY, + }, + tracker: models.Tracker{ + PubID: 12345, + PageURL: "www.abc.com", + IID: "98765", + ProfileID: "123", + VersionID: "1", + SlotID: "1234_1234", + Adunit: "adunit", + Platform: 1, + Origin: "www.publisher.com", + TestGroup: 1, + AdPodSlot: 0, + FloorSkippedFlag: ptrutil.ToPtr(0), + FloorModelVersion: "test version", + FloorSource: ptrutil.ToPtr(1), + FloorType: 1, + RewardedInventory: 1, + Secure: 1, + SSAI: "mediatailor", + PartnerInfo: models.Partner{ + PartnerID: "AppNexus", + BidderCode: "AppNexus1", + BidID: "6521", + OrigBidID: "6521", + GrossECPM: 4.3, + NetECPM: 2.5, + KGPV: "adunit@300x250", + AdDuration: 10, + Adformat: models.Banner, + AdSize: "300x250", + ServerSide: 1, + Advertiser: "fb.com", + DealID: "420", + FloorValue: 4.4, + FloorRuleValue: 2, + }, + }, + }, + want: "https://t.pubmatic.com/wt?adv=fb.com&af=banner&aps=0&au=adunit&bc=AppNexus1&bidid=6521&di=420&dur=10&eg=4.3&en=2.5&fmv=test version&frv=2&fskp=0&fsrc=1&ft=1&fv=4.4&iid=98765&kgpv=adunit@300x250&orig=www.publisher.com&origbidid=6521&pdvid=1&pid=123&plt=1&pn=AppNexus&psz=300x250&pubid=12345&purl=www.abc.com&rwrd=1&sl=1&slot=1234_1234&ss=1&ssai=mediatailor&tgid=1&tst=0", + }, + { + name: "all_details_with_secure_enable_in_tracker", + args: args{ + rctx: models.RequestCtx{ + TrackerEndpoint: "//t.pubmatic.com/wt", + Platform: models.PLATFORM_DISPLAY, + }, + tracker: models.Tracker{ + PubID: 12345, + PageURL: "www.abc.com", + IID: "98765", + ProfileID: "123", + VersionID: "1", + SlotID: "1234_1234", + Adunit: "adunit", + Platform: 1, + Origin: "www.publisher.com", + TestGroup: 1, + AdPodSlot: 0, + FloorSkippedFlag: ptrutil.ToPtr(0), + FloorModelVersion: "test version", + FloorSource: ptrutil.ToPtr(1), + FloorType: 1, + RewardedInventory: 1, + Secure: 1, + PartnerInfo: models.Partner{ + PartnerID: "AppNexus", + BidderCode: "AppNexus1", + BidID: "6521", + OrigBidID: "6521", + GrossECPM: 4.3, + NetECPM: 2.5, + KGPV: "adunit@300x250", + AdDuration: 10, + Adformat: models.Banner, + AdSize: "300x250", + ServerSide: 1, + Advertiser: "fb.com", + DealID: "420", + FloorValue: 4.4, + FloorRuleValue: 2, + }, + }, + }, + want: "https://t.pubmatic.com/wt?adv=fb.com&af=banner&aps=0&au=adunit&bc=AppNexus1&bidid=6521&di=420&dur=10&eg=4.3&en=2.5&fmv=test version&frv=2&fskp=0&fsrc=1&ft=1&fv=4.4&iid=98765&kgpv=adunit@300x250&orig=www.publisher.com&origbidid=6521&pdvid=1&pid=123&plt=1&pn=AppNexus&psz=300x250&pubid=12345&purl=www.abc.com&rwrd=1&sl=1&slot=1234_1234&ss=1&tgid=1&tst=0", + }, + { + name: "all_details_with_RewardInventory_in_tracker", + args: args{ + rctx: models.RequestCtx{ + TrackerEndpoint: "//t.pubmatic.com/wt", + Platform: models.PLATFORM_APP, + }, + tracker: models.Tracker{ + PubID: 12345, + PageURL: "www.abc.com", + IID: "98765", + ProfileID: "123", + VersionID: "1", + SlotID: "1234_1234", + Adunit: "adunit", + Platform: 1, + Origin: "www.publisher.com", + TestGroup: 1, + AdPodSlot: 0, + FloorSkippedFlag: ptrutil.ToPtr(0), + FloorModelVersion: "test version", + FloorSource: ptrutil.ToPtr(1), + FloorType: 1, + RewardedInventory: 1, + PartnerInfo: models.Partner{ + PartnerID: "AppNexus", + BidderCode: "AppNexus1", + BidID: "6521", + OrigBidID: "6521", + GrossECPM: 4.3, + NetECPM: 2.5, + KGPV: "adunit@300x250", + AdDuration: 10, + Adformat: models.Banner, + AdSize: "300x250", + ServerSide: 1, + Advertiser: "fb.com", + DealID: "420", + FloorValue: 4.4, + FloorRuleValue: 2, + }, + }, + }, + want: "//t.pubmatic.com/wt?adv=fb.com&af=banner&aps=0&au=adunit&bc=AppNexus1&bidid=6521&di=420&dur=10&eg=4.3&en=2.5&fmv=test version&frv=2&fskp=0&fsrc=1&ft=1&fv=4.4&iid=98765&kgpv=adunit@300x250&orig=www.publisher.com&origbidid=6521&pdvid=1&pid=123&plt=1&pn=AppNexus&psz=300x250&pubid=12345&purl=www.abc.com&rwrd=1&sl=1&slot=1234_1234&ss=1&tgid=1&tst=0", + }, + { + name: "all_floors_details_in_tracker", + args: args{ + rctx: models.RequestCtx{ + TrackerEndpoint: "//t.pubmatic.com/wt", + Platform: models.PLATFORM_APP, + }, + tracker: models.Tracker{ + PubID: 12345, + PageURL: "www.abc.com", + IID: "98765", + ProfileID: "123", + VersionID: "1", + SlotID: "1234_1234", + Adunit: "adunit", + Platform: 1, + Origin: "www.publisher.com", + TestGroup: 1, + AdPodSlot: 0, + FloorSkippedFlag: ptrutil.ToPtr(0), + FloorModelVersion: "test version", + FloorSource: ptrutil.ToPtr(1), + FloorType: 1, + PartnerInfo: models.Partner{ + PartnerID: "AppNexus", + BidderCode: "AppNexus1", + BidID: "6521", + OrigBidID: "6521", + GrossECPM: 4.3, + NetECPM: 2.5, + KGPV: "adunit@300x250", + AdDuration: 10, + Adformat: models.Banner, + AdSize: "300x250", + ServerSide: 1, + Advertiser: "fb.com", + DealID: "420", + FloorValue: 4.4, + FloorRuleValue: 2, + }, + }, + }, + want: "//t.pubmatic.com/wt?adv=fb.com&af=banner&aps=0&au=adunit&bc=AppNexus1&bidid=6521&di=420&dur=10&eg=4.3&en=2.5&fmv=test version&frv=2&fskp=0&fsrc=1&ft=1&fv=4.4&iid=98765&kgpv=adunit@300x250&orig=www.publisher.com&origbidid=6521&pdvid=1&pid=123&plt=1&pn=AppNexus&psz=300x250&pubid=12345&purl=www.abc.com&sl=1&slot=1234_1234&ss=1&tgid=1&tst=0", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + trackerUrl := constructTrackerURL(tt.args.rctx, tt.args.tracker) + decodedTrackerUrl, _ := url.QueryUnescape(trackerUrl) + assert.Equal(t, tt.want, decodedTrackerUrl, tt.name) + }) + } +} + +func TestConstructVideoErrorURL(t *testing.T) { + type args struct { + rctx models.RequestCtx + errorURLString string + bid openrtb2.Bid + tracker models.Tracker + } + tests := []struct { + name string + args args + want string + prefix string + }{ + { + name: "empty_urlString", + args: args{ + rctx: models.RequestCtx{}, + errorURLString: "", + bid: openrtb2.Bid{}, + tracker: models.Tracker{}, + }, + want: "", + prefix: "", + }, + { + name: "invalid_urlString_with_parsing_error", + args: args{ + rctx: models.RequestCtx{}, + errorURLString: `:invalid_url`, + bid: openrtb2.Bid{}, + tracker: models.Tracker{}, + }, + want: "", + prefix: "", + }, + { + name: "invalid_urlString_with_parsing", + args: args{ + rctx: models.RequestCtx{}, + errorURLString: `invalid_url`, + bid: openrtb2.Bid{}, + tracker: models.Tracker{}, + }, + want: "", + prefix: "", + }, + { + name: "valid_video_errorUrl", + args: args{ + rctx: models.RequestCtx{ + Origin: "domain.com:8080", + }, + errorURLString: `//t.pubmatic.com/wt`, + bid: openrtb2.Bid{}, + tracker: models.Tracker{ + PubID: 12345, + PageURL: "www.abc.com", + IID: "98765", + ProfileID: "123", + VersionID: "1", + SlotID: "1234_1234", + Adunit: "adunit", + Platform: 1, + Origin: "www.publisher.com", + TestGroup: 1, + AdPodSlot: 0, + SSAI: "mediatailor", + PartnerInfo: models.Partner{ + PartnerID: "AppNexus", + BidderCode: "AppNexus1", + BidID: "6521", + OrigBidID: "6521", + GrossECPM: 4.3, + NetECPM: 2.5, + KGPV: "adunit@300x250", + AdDuration: 10, + Adformat: models.Banner, + AdSize: "300x250", + ServerSide: 1, + Advertiser: "fb.com", + DealID: "420", + }, + }, + }, + want: `https://t.pubmatic.com/wt?operId=8&crId=-1&p=12345&pid=123&pn=AppNexus&ts=0&v=1&ier=[ERRORCODE]&bc=AppNexus1&au=adunit&sURL=domain.com%253A8080&ssai=mediatailor&pfi=1&adv=fb.com`, + prefix: `https://t.pubmatic.com/wt?operId=8`, + }, + { + name: "URL_with_Constant_Parameter", + args: args{ + rctx: models.RequestCtx{ + Origin: "domain.com:8080", + }, + errorURLString: `//t.pubmatic.com/wt?p1=v1&p2=v2`, + bid: openrtb2.Bid{}, + tracker: models.Tracker{ + PubID: 12345, + PageURL: "www.abc.com", + IID: "98765", + ProfileID: "123", + VersionID: "1", + SlotID: "1234_1234", + Adunit: "adunit", + Platform: 1, + Origin: "www.publisher.com", + TestGroup: 1, + AdPodSlot: 0, + SSAI: "mediatailor", + PartnerInfo: models.Partner{ + PartnerID: "AppNexus", + BidderCode: "AppNexus1", + BidID: "6521", + OrigBidID: "6521", + GrossECPM: 4.3, + NetECPM: 2.5, + KGPV: "adunit@300x250", + AdDuration: 10, + Adformat: models.Banner, + AdSize: "300x250", + ServerSide: 1, + Advertiser: "fb.com", + DealID: "420", + }, + }, + }, + want: `https://t.pubmatic.com/wt?operId=8&p1=v1&p2=v2&crId=-1&p=12345&pid=123&pn=AppNexus&ts=0&v=1&ier=[ERRORCODE]&bc=AppNexus1&au=adunit&sURL=domain.com%253A8080&ssai=mediatailor&pfi=1&adv=fb.com`, + prefix: `https://t.pubmatic.com/wt?operId=8`, + }, + { + name: "Creative_ID_in_bid", + args: args{ + rctx: models.RequestCtx{ + Origin: "domain.com:8080", + }, + errorURLString: `//t.pubmatic.com/wt`, + bid: openrtb2.Bid{ + CrID: "cr123", + }, + tracker: models.Tracker{ + PubID: 12345, + PageURL: "www.abc.com", + IID: "98765", + ProfileID: "123", + VersionID: "1", + SlotID: "1234_1234", + Adunit: "adunit", + Platform: 1, + Origin: "www.publisher.com", + TestGroup: 1, + AdPodSlot: 0, + SSAI: "mediatailor", + PartnerInfo: models.Partner{ + PartnerID: "AppNexus", + BidderCode: "AppNexus1", + BidID: "6521", + OrigBidID: "6521", + GrossECPM: 4.3, + NetECPM: 2.5, + KGPV: "adunit@300x250", + AdDuration: 10, + Adformat: models.Banner, + AdSize: "300x250", + ServerSide: 1, + Advertiser: "fb.com", + DealID: "420", + }, + }, + }, + want: `https://t.pubmatic.com/wt?operId=8&crId=cr123&p=12345&pid=123&pn=AppNexus&ts=0&v=1&ier=[ERRORCODE]&bc=AppNexus1&au=adunit&sURL=domain.com%253A8080&ssai=mediatailor&pfi=1&adv=fb.com`, + prefix: `https://t.pubmatic.com/wt?operId=8`, + }, + { + name: "URL_with_Schema", + args: args{ + rctx: models.RequestCtx{ + Origin: "com.myapp.test", + }, + errorURLString: `http://t.pubmatic.com/wt?p1=v1&p2=v2`, + bid: openrtb2.Bid{}, + tracker: models.Tracker{ + PubID: 12345, + PageURL: "www.abc.com", + IID: "98765", + ProfileID: "123", + VersionID: "1", + SlotID: "1234_1234", + Adunit: "adunit", + Platform: 1, + Origin: "www.publisher.com", + TestGroup: 1, + AdPodSlot: 0, + SSAI: "mediatailor", + PartnerInfo: models.Partner{ + PartnerID: "AppNexus", + BidderCode: "AppNexus1", + BidID: "6521", + OrigBidID: "6521", + GrossECPM: 4.3, + NetECPM: 2.5, + KGPV: "adunit@300x250", + AdDuration: 10, + Adformat: models.Banner, + AdSize: "300x250", + ServerSide: 1, + Advertiser: "fb.com", + DealID: "420", + }, + }, + }, + want: `https://t.pubmatic.com/wt?operId=8&p1=v1&p2=v2&crId=-1&p=12345&pid=123&pn=AppNexus&ts=0&v=1&ier=[ERRORCODE]&bc=AppNexus1&au=adunit&sURL=com.myapp.test&ssai=mediatailor&pfi=1&adv=fb.com`, + prefix: `https://t.pubmatic.com/wt?operId=8`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + constructedURL := constructVideoErrorURL(tt.args.rctx, tt.args.errorURLString, tt.args.bid, tt.args.tracker) + if len(constructedURL) > 0 && len(tt.want) > 0 { + wantURL, _ := url.Parse(constructedURL) + expectedURL, _ := url.Parse(tt.want) + if wantURL != nil && expectedURL != nil { + assert.Equal(t, wantURL.Host, expectedURL.Host) + assert.Equal(t, wantURL.Query(), expectedURL.Query()) + assert.Contains(t, constructedURL, tt.prefix) + } + } + }) + } +} + +func TestCreateTrackers(t *testing.T) { + startTime := time.Now().Unix() + type args struct { + rctx models.RequestCtx + bidResponse *openrtb2.BidResponse + } + tests := []struct { + name string + args args + want map[string]models.OWTracker + }{ + { + name: "overwrite marketplace bid details", + args: args{ + rctx: func() models.RequestCtx { + testRctx := rctx + testRctx.StartTime = startTime + return testRctx + }(), + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bidID-1", + ImpID: "impID-1", + Price: 8.7, + W: 250, + H: 300, + ADomain: []string{"domain.com"}, + DealID: "deal-id-1", + }, + }, + Seat: "pubmatic", + }, + }, + Cur: models.USD, + }, + }, + want: map[string]models.OWTracker{ + "bidID-1": { + Tracker: models.Tracker{ + PubID: 5890, + PageURL: "abc.com", + Timestamp: startTime, + IID: "loggerIID", + ProfileID: "1234", + VersionID: "1", + Adunit: "adunit-1", + SlotID: "impID-1_adunit-1", + PartnerInfo: models.Partner{ + PartnerID: "pubmatic", + BidderCode: "pubmatic", + KGPV: "adunit-1@250x300", + GrossECPM: 8.7, + NetECPM: 8.7, + BidID: "bidID-1", + OrigBidID: "bidID-1", + AdSize: "250x300", + AdDuration: 20, + Adformat: "banner", + ServerSide: 1, + Advertiser: "domain.com", + FloorValue: 6.4, + FloorRuleValue: 4.4, + DealID: "deal-id-1", + }, + Platform: 5, + SSAI: "mediatailor", + AdPodSlot: 0, + TestGroup: 1, + Origin: "publisher.com", + ImpID: "impID-1", + LoggerData: models.LoggerData{ + KGPSV: "adunit-1@250x300", + }, + }, + TrackerURL: "https:?adv=domain.com&af=banner&aps=0&au=adunit-1&bc=pubmatic&bidid=bidID-1&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=pubmatic&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, + PriceModel: "CPM", + PriceCurrency: "USD", + BidType: "banner", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := CreateTrackers(tt.args.rctx, tt.args.bidResponse) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/tracker/inject.go b/modules/pubmatic/openwrap/tracker/inject.go new file mode 100644 index 00000000000..da9436e046c --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/inject.go @@ -0,0 +1,79 @@ +package tracker + +import ( + "errors" + "fmt" + "strings" + + "golang.org/x/exp/slices" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" +) + +func InjectTrackers(rctx models.RequestCtx, bidResponse *openrtb2.BidResponse) (*openrtb2.BidResponse, error) { + var errs error + for i, seatBid := range bidResponse.SeatBid { + for j, bid := range seatBid.Bid { + var errMsg string + var err error + tracker := rctx.Trackers[bid.ID] + adformat := tracker.BidType + if rctx.Platform == models.PLATFORM_VIDEO { + adformat = "video" + } + pixels := getUniversalPixels(rctx, adformat, seatBid.Seat) + + switch adformat { + case models.Banner: + bidResponse.SeatBid[i].Bid[j].AdM = injectBannerTracker(rctx, tracker, bid, seatBid.Seat, pixels) + case models.Video: + trackers := []models.OWTracker{tracker} + bidResponse.SeatBid[i].Bid[j].AdM, err = injectVideoCreativeTrackers(bid, trackers) + case models.Native: + if impBidCtx, ok := rctx.ImpBidCtx[bid.ImpID]; ok { + bidResponse.SeatBid[i].Bid[j].AdM, err = injectNativeCreativeTrackers(impBidCtx.Native, bid.AdM, tracker) + } else { + errMsg = fmt.Sprintf("native obj not found for impid %s", bid.ImpID) + } + default: + errMsg = fmt.Sprintf("Invalid adformat %s for bidid %s", adformat, bid.ID) + } + + if err != nil { + errMsg = fmt.Sprintf("failed to inject tracker for bidid %s with error %s", bid.ID, err.Error()) + } + if errMsg != "" { + rctx.MetricsEngine.RecordInjectTrackerErrorCount(adformat, rctx.PubIDStr, seatBid.Seat) + errs = models.ErrorWrap(errs, errors.New(errMsg)) + } + + } + } + return bidResponse, errs +} + +func getUniversalPixels(rctx models.RequestCtx, adformat string, bidderCode string) []adunitconfig.UniversalPixel { + var pixels, upixels []adunitconfig.UniversalPixel + if rctx.AdUnitConfig != nil && rctx.AdUnitConfig.Config != nil { + if defaultAdUnitConfig, ok := rctx.AdUnitConfig.Config[models.AdunitConfigDefaultKey]; ok && defaultAdUnitConfig != nil && len(defaultAdUnitConfig.UniversalPixel) != 0 { + upixels = defaultAdUnitConfig.UniversalPixel + } + } + for _, pixelVal := range upixels { + if pixelVal.MediaType != adformat { + continue + } + if len(pixelVal.Partners) > 0 && !slices.Contains(pixelVal.Partners, bidderCode) { + continue + } + pixel := pixelVal.Pixel // for pixelType `js` + if pixelVal.PixelType == models.PixelTypeUrl { + pixel = strings.Replace(models.UniversalPixelMacroForUrl, "${pixelUrl}", pixelVal.Pixel, 1) + } + pixelVal.Pixel = pixel + pixels = append(pixels, pixelVal) + } + return pixels +} diff --git a/modules/pubmatic/openwrap/tracker/inject_test.go b/modules/pubmatic/openwrap/tracker/inject_test.go new file mode 100644 index 00000000000..3cfb2893b9d --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/inject_test.go @@ -0,0 +1,688 @@ +package tracker + +import ( + "reflect" + "testing" + + "github.com/golang/mock/gomock" + "github.com/magiconair/properties/assert" + "github.com/prebid/openrtb/v19/openrtb2" + mock_cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache/mock" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/tbf" +) + +func TestInjectTrackers(t *testing.T) { + ctrl := gomock.NewController(t) + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockCache := mock_cache.NewMockCache(ctrl) + defer ctrl.Finish() + tbf.SetAndResetTBFConfig(mockCache, map[int]map[int]int{ + 5890: {1234: 100}, + }) + models.TrackerCallWrapOMActive = `` + + type args struct { + rctx models.RequestCtx + bidResponse *openrtb2.BidResponse + } + tests := []struct { + name string + args args + want *openrtb2.BidResponse + wantErr bool + }{ + { + name: "no_bidresponse", + args: args{ + bidResponse: &openrtb2.BidResponse{}, + }, + want: &openrtb2.BidResponse{}, + wantErr: false, + }, + { + name: "tracker_params_missing", + args: args{ + rctx: models.RequestCtx{ + Platform: "video", + Trackers: map[string]models.OWTracker{}, + MetricsEngine: mockEngine, + }, + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `creative`, + }, + }, + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `creative`, + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "invalid_adformat", + args: args{ + rctx: models.RequestCtx{ + Trackers: map[string]models.OWTracker{ + "12345": { + BidType: "invalid", + TrackerURL: `Tracking URL`, + }, + }, + MetricsEngine: mockEngine, + }, + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `creative`, + }, + }, + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `creative`, + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "empty_tracker_params", + args: args{ + rctx: models.RequestCtx{ + MetricsEngine: mockEngine, + }, + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `creative`, + }, + }, + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `creative`, + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "adformat_is_banner", + args: args{ + rctx: models.RequestCtx{ + Platform: "", + Trackers: map[string]models.OWTracker{ + "12345": { + BidType: "banner", + TrackerURL: `Tracking URL`, + }, + }, + }, + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `sample_creative`, + }, + }, + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `sample_creative
`, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "adformat_is_video", + args: args{ + rctx: models.RequestCtx{ + Platform: "", + Trackers: map[string]models.OWTracker{ + "12345": { + BidType: "video", + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + }, + }, + }, + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: ``, + }, + }, + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: ``, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "platform_is_video", + args: args{ + rctx: models.RequestCtx{ + Platform: "video", + Trackers: map[string]models.OWTracker{ + "12345": { + BidType: "video", + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + }, + }, + }, + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: ``, + }, + }, + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: ``, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "platform_is_app", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + Trackers: map[string]models.OWTracker{ + "12345": { + BidType: "banner", + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + }, + }, + }, + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `sample_creative`, + }, + }, + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `sample_creative
`, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "platform_is_app_with_OM_Inactive_pubmatic", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + Trackers: map[string]models.OWTracker{ + "12345": { + BidType: "banner", + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + DspId: -1, + }, + }, + }, + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `sample_creative`, + }, + }, + Seat: models.BidderPubMatic, + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `sample_creative
`, + }, + }, + Seat: models.BidderPubMatic, + }, + }, + }, + wantErr: false, + }, + { + name: "platform_is_app_with_OM_active_pubmatic", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + Trackers: map[string]models.OWTracker{ + "12345": { + BidType: "banner", + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + DspId: models.DspId_DV360, + }, + }, + }, + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `sample_creative`, + }, + }, + Seat: models.BidderPubMatic, + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `sample_creative`, + }, + }, + Seat: models.BidderPubMatic, + }, + }, + }, + wantErr: false, + }, + { + name: "native_obj_not_found", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + Trackers: map[string]models.OWTracker{ + "12345": { + BidType: "native", + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + }, + }, + MetricsEngine: mockEngine, + ImpBidCtx: map[string]models.ImpCtx{}, + }, + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + ImpID: "imp123", + AdM: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}]}`, + }, + }, + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + ImpID: "imp123", + AdM: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}]}`, + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "adformat_is_native", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + Trackers: map[string]models.OWTracker{ + "12345": { + BidType: "native", + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp123": { + Native: &openrtb2.Native{ + Request: "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":0,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":0,\"data\":{\"type\":1,\"len\":2}},{\"id\":2,\"required\":0,\"img\":{\"type\":1,\"w\":50,\"h\":50}},{\"id\":3,\"required\":0,\"title\":{\"len\":80}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":2}}]}", + }, + }, + }, + }, + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + ImpID: "imp123", + AdM: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}]}`, + }, + }, + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + ImpID: "imp123", + AdM: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"},{"event":1,"method":1,"url":"Tracking URL"}]}`, + }, + }, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args.rctx.MetricsEngine != nil { + mockEngine.EXPECT().RecordInjectTrackerErrorCount(gomock.Any(), gomock.Any(), gomock.Any()) + } + got, err := InjectTrackers(tt.args.rctx, tt.args.bidResponse) + if (err != nil) != tt.wantErr { + t.Errorf("InjectTrackers() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("InjectTrackers() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getUniversalPixels(t *testing.T) { + type args struct { + rctx models.RequestCtx + adFormat string + bidderCode string + } + tests := []struct { + name string + args args + want []adunitconfig.UniversalPixel + }{ + { + name: "No default in adunitconfig", + args: args{ + adFormat: models.Banner, + bidderCode: models.BidderPubMatic, + rctx: models.RequestCtx{ + AdUnitConfig: &adunitconfig.AdUnitConfig{ + Config: map[string]*adunitconfig.AdConfig{}, + }, + }, + }, + want: nil, + }, + { + name: "No partners", + args: args{ + adFormat: models.Banner, + bidderCode: models.BidderPubMatic, + rctx: models.RequestCtx{ + AdUnitConfig: &adunitconfig.AdUnitConfig{ + Config: map[string]*adunitconfig.AdConfig{ + "default": { + UniversalPixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: models.PixelTypeUrl, + Pos: models.PixelPosAbove, + MediaType: "banner", + }, + }, + }, + }, + }, + }, + }, + want: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: `
`, + PixelType: models.PixelTypeUrl, + Pos: models.PixelPosAbove, + MediaType: "banner", + }, + }, + }, + { + name: "partner not present", + args: args{ + adFormat: models.Banner, + bidderCode: models.BidderPubMatic, + rctx: models.RequestCtx{ + AdUnitConfig: &adunitconfig.AdUnitConfig{ + Config: map[string]*adunitconfig.AdConfig{ + "default": { + UniversalPixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: models.PixelTypeUrl, + Pos: models.PixelPosAbove, + MediaType: models.Banner, + Partners: []string{"appnexus"}, + }, + }, + }, + }, + }, + }, + }, + want: nil, + }, + { + name: "mismatch in adformat and mediatype", + args: args{ + adFormat: models.Banner, + rctx: models.RequestCtx{ + AdUnitConfig: &adunitconfig.AdUnitConfig{ + Config: map[string]*adunitconfig.AdConfig{ + "default": { + UniversalPixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: models.PixelTypeJS, + Pos: models.PixelPosAbove, + MediaType: "video", + Partners: []string{"pubmatic", "appnexus"}, + }, + }, + }, + }, + }, + }, + }, + want: nil, + }, + { + name: "send valid upixel", + args: args{ + bidderCode: models.BidderPubMatic, + adFormat: models.Banner, + rctx: models.RequestCtx{ + AdUnitConfig: &adunitconfig.AdUnitConfig{ + Config: map[string]*adunitconfig.AdConfig{ + "default": { + UniversalPixel: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: "sample.com", + PixelType: "url", + Pos: models.PixelPosAbove, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + { + Id: 123, + Pixel: "", + PixelType: models.PixelTypeJS, + Pos: models.PixelPosBelow, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + { + Id: 123, + Pixel: "sample.com", + PixelType: "url", + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + }, + }, + }, + }, + }, + }, + want: []adunitconfig.UniversalPixel{ + { + Id: 123, + Pixel: `
`, + PixelType: "url", + Pos: models.PixelPosAbove, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + { + Id: 123, + Pixel: "", + PixelType: models.PixelTypeJS, + Pos: models.PixelPosBelow, + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + { + Id: 123, + Pixel: `
`, + PixelType: "url", + MediaType: "banner", + Partners: []string{"pubmatic", "appnexus"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getUniversalPixels(tt.args.rctx, tt.args.adFormat, tt.args.bidderCode) + assert.Equal(t, got, tt.want) + }) + } +} diff --git a/modules/pubmatic/openwrap/tracker/models.go b/modules/pubmatic/openwrap/tracker/models.go new file mode 100644 index 00000000000..fd94fcab9fe --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/models.go @@ -0,0 +1,8 @@ +package tracker + +func getRewardedInventoryFlag(reward *int8) int { + if reward != nil { + return int(*reward) + } + return 0 +} diff --git a/modules/pubmatic/openwrap/tracker/models_test.go b/modules/pubmatic/openwrap/tracker/models_test.go new file mode 100644 index 00000000000..bf0317392f4 --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/models_test.go @@ -0,0 +1 @@ +package tracker diff --git a/modules/pubmatic/openwrap/tracker/native.go b/modules/pubmatic/openwrap/tracker/native.go new file mode 100644 index 00000000000..54bcec3000a --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/native.go @@ -0,0 +1,77 @@ +package tracker + +import ( + "errors" + "fmt" + "strings" + + "github.com/buger/jsonparser" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +// Inject TrackerCall in Native Adm +func injectNativeCreativeTrackers(native *openrtb2.Native, adm string, tracker models.OWTracker) (string, error) { + if native == nil { + return adm, errors.New("native object is missing") + } + if len(native.Request) == 0 { + return adm, errors.New("native request is empty") + } + setTrackerURL := false + callback := func(value []byte, dataType jsonparser.ValueType, offset int, err error) { + if err != nil { + return + } + if setTrackerURL { + return + } + adm, setTrackerURL = injectNativeEventTracker(&adm, value, tracker) + } + jsonparser.ArrayEach([]byte(native.Request), callback, models.EventTrackers) + + if setTrackerURL { + return adm, nil + } + return injectNativeImpressionTracker(&adm, tracker) +} + +// inject tracker in EventTracker Object +func injectNativeEventTracker(adm *string, value []byte, trackerParam models.OWTracker) (string, bool) { + //Check for event=1 + event, _, _, err := jsonparser.Get(value, models.Event) + if err != nil || string(event) != models.EventValue { + return *adm, false + } + //Check for method=1 + methodsArray, _, _, err := jsonparser.Get(value, models.Methods) // "[1]","[2]","[1,2]", "[2,1]" + if err != nil || !strings.Contains(string(methodsArray), models.MethodValue) { + return *adm, false + } + + nativeEventTracker := strings.Replace(models.NativeTrackerMacro, "${trackerUrl}", trackerParam.TrackerURL, 1) + newAdm, err := jsonparser.Set([]byte(*adm), []byte(nativeEventTracker), models.EventTrackers, "[]") + if err != nil { + return *adm, false + } + *adm = string(newAdm) + return *adm, true +} + +// inject tracker in ImpTracker Object +func injectNativeImpressionTracker(adm *string, tracker models.OWTracker) (string, error) { + impTrackers := []string{} + callback := func(value []byte, dataType jsonparser.ValueType, offset int, err error) { + impTrackers = append(impTrackers, string(value)) + } + jsonparser.ArrayEach([]byte(*adm), callback, models.ImpTrackers) + //append trackerUrl + impTrackers = append(impTrackers, tracker.TrackerURL) + allImpTrackers := fmt.Sprintf(`["%s"]`, strings.Join(impTrackers, `","`)) + newAdm, err := jsonparser.Set([]byte(*adm), []byte(allImpTrackers), models.ImpTrackers) + if err != nil { + return *adm, errors.New("error setting imptrackers in native adm") + } + *adm = string(newAdm) + return *adm, nil +} diff --git a/modules/pubmatic/openwrap/tracker/native_test.go b/modules/pubmatic/openwrap/tracker/native_test.go new file mode 100644 index 00000000000..7b26bcbd2eb --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/native_test.go @@ -0,0 +1,331 @@ +package tracker + +import ( + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/util/ptrutil" +) + +func Test_injectNativeCreativeTrackers(t *testing.T) { + type args struct { + native *openrtb2.Native + adm string + tracker models.OWTracker + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "native_object_nil", + args: args{ + adm: `creative`, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `creative`, + wantErr: true, + }, + { + name: "empty_native_object", + args: args{ + native: &openrtb2.Native{}, + adm: `creative`, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `creative`, + wantErr: true, + }, + { + name: "error_injecting_impression_tracker", + args: args{ + native: &openrtb2.Native{ + Request: `request`, + }, + adm: `creative`, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `creative`, + wantErr: true, + }, + { + name: "no_eventTracker_in_req_but_impTracker_exists_in_adm", + args: args{ + native: &openrtb2.Native{ + Request: "{\"context\":1,\"plcmttype\":1,\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":0,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":0,\"data\":{\"type\":1,\"len\":2}},{\"id\":2,\"required\":0,\"img\":{\"type\":1,\"w\":50,\"h\":50}},{\"id\":3,\"required\":0,\"title\":{\"len\":80}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":2}}]}", + }, + adm: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}]}`, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35","Tracking URL"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}]}`, + wantErr: false, + }, + { + name: "none_of_eventTracker_and_impTracker_in_req", + args: args{ + native: &openrtb2.Native{ + Request: "{\"context\":1,\"plcmttype\":1,\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":0,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":0,\"data\":{\"type\":1,\"len\":2}},{\"id\":2,\"required\":0,\"img\":{\"type\":1,\"w\":50,\"h\":50}},{\"id\":3,\"required\":0,\"title\":{\"len\":80}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":2}}]}", + }, + adm: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}]}`, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}],"imptrackers":["Tracking URL"]}`, + wantErr: false, + }, + { + name: "event_is_1_&_method_is_2", + args: args{ + native: &openrtb2.Native{ + Request: "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":0,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":0,\"data\":{\"type\":1,\"len\":2}},{\"id\":2,\"required\":0,\"img\":{\"type\":1,\"w\":50,\"h\":50}},{\"id\":3,\"required\":0,\"title\":{\"len\":80}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":2}}]}", + }, + adm: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":2,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet.js"}]}`, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":2,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet.js"}],"imptrackers":["Tracking URL"]}`, + wantErr: false, + }, + { + name: "event_is_2_&_method_is_1", + args: args{ + native: &openrtb2.Native{ + Request: "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":2,\"methods\":[1]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":0,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":0,\"data\":{\"type\":1,\"len\":2}},{\"id\":2,\"required\":0,\"img\":{\"type\":1,\"w\":50,\"h\":50}},{\"id\":3,\"required\":0,\"title\":{\"len\":80}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":2}}]}", + }, + adm: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["abc.com"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":2,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet.php"}]}`, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["abc.com","Tracking URL"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":2,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet.php"}]}`, + wantErr: false, + }, + { + name: "event_1_&_method_1_but_eventtracker_exist_in_adm", + args: args{ + native: &openrtb2.Native{ + Request: "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":0,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":0,\"data\":{\"type\":1,\"len\":2}},{\"id\":2,\"required\":0,\"img\":{\"type\":1,\"w\":50,\"h\":50}},{\"id\":3,\"required\":0,\"title\":{\"len\":80}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":2}}]}", + }, + adm: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}]}`, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"},{"event":1,"method":1,"url":"Tracking URL"}]}`, + wantErr: false, + }, + { + name: "event_1_&_method_1&2_but_eventtracker_exist_in_adm", + args: args{ + native: &openrtb2.Native{ + Request: "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":0,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":0,\"data\":{\"type\":1,\"len\":2}},{\"id\":2,\"required\":0,\"img\":{\"type\":1,\"w\":50,\"h\":50}},{\"id\":3,\"required\":0,\"title\":{\"len\":80}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":2}}]}", + }, + adm: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}]}`, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"},{"event":1,"method":1,"url":"Tracking URL"}]}`, + wantErr: false, + }, + { + name: "event_1_&_method_1_but_eventtracker_NOT_exist_in_adm", + args: args{ + native: &openrtb2.Native{ + Request: "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":0,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":0,\"data\":{\"type\":1,\"len\":2}},{\"id\":2,\"required\":0,\"img\":{\"type\":1,\"w\":50,\"h\":50}},{\"id\":3,\"required\":0,\"title\":{\"len\":80}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":2}}]}", + }, + adm: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e"}`, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"Tracking URL"}]}`, + wantErr: false, + }, + { + name: "event_1_method_1&2_and_multiple_eventrackers", + args: args{ + native: &openrtb2.Native{ + Request: "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":0,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":0,\"data\":{\"type\":1,\"len\":2}},{\"id\":2,\"required\":0,\"img\":{\"type\":1,\"w\":50,\"h\":50}},{\"id\":3,\"required\":0,\"title\":{\"len\":80}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":2}}]}", + }, + adm: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"},{"event":1,"method":1,"url":"abc.com"}]}`, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"},{"event":1,"method":1,"url":"abc.com"},{"event":1,"method":1,"url":"Tracking URL"}]}`, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := injectNativeCreativeTrackers(tt.args.native, tt.args.adm, tt.args.tracker) + if (err != nil) != tt.wantErr { + t.Errorf("injectNativeCreativeTrackers() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("injectNativeCreativeTrackers() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_injectNativeEventTracker(t *testing.T) { + type args struct { + adm *string + value []byte + trackerParam models.OWTracker + } + tests := []struct { + name string + args args + want string + want1 bool + }{ + { + name: "error_injecting_eventTracker_empty_adm", + args: args{ + value: []byte("{\"event\":1,\"methods\":[1]}"), + adm: ptrutil.ToPtr(""), + trackerParam: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: "", + want1: false, + }, + { + name: "event is 1 & method 2", + args: args{ + value: []byte("{\"event\":1,\"methods\":[2]}"), + adm: ptrutil.ToPtr(`{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":2,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet.js"}]}`), + trackerParam: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":2,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet.js"}]}`, + want1: false, + }, + { + name: "event is 2 & method 1", + args: args{ + value: []byte("{\"event\":2,\"methods\":[1]}"), + adm: ptrutil.ToPtr(`{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":2,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet.php"}]}`), + trackerParam: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":2,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet.php"}]}`, + want1: false, + }, + { + name: "event 1 & method 1, eventtracker exist in adm", + args: args{ + value: []byte("{\"event\":1,\"methods\":[1]}"), + adm: ptrutil.ToPtr(`{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}]}`), + trackerParam: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"},{"event":1,"method":1,"url":"Tracking URL"}]}`, + want1: true, + }, + { + name: "event 1 & method 1,2, eventtracker exist in adm", + args: args{ + value: []byte("{\"event\":1,\"methods\":[1,2]}"), + adm: ptrutil.ToPtr(`{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}]}`), + trackerParam: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"},{"event":1,"method":1,"url":"Tracking URL"}]}`, + want1: true, + }, + { + name: "event 1 & method 1, eventtracker NOT exist in adm", + args: args{ + value: []byte("{\"event\":1,\"methods\":[1]}"), + adm: ptrutil.ToPtr(`{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e"}`), + trackerParam: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"Tracking URL"}]}`, + want1: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := injectNativeEventTracker(tt.args.adm, tt.args.value, tt.args.trackerParam) + if got != tt.want { + t.Errorf("injectNativeEventTracker() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("injectNativeEventTracker() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func Test_injectNativeImpressionTracker(t *testing.T) { + type args struct { + adm *string + tracker models.OWTracker + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "error_injecting_impression_tracker_empty_adm", + args: args{ + adm: ptrutil.ToPtr(""), + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: "", + wantErr: true, + }, + { + name: "no_eventTracker_in_req_but_impTracker_exists_in_adm", + args: args{ + adm: ptrutil.ToPtr(`{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}]}`), + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + }, + want: `{"assets":[{"id":0,"img":{"type":3,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":1,"data":{"type":1,"value":"Sponsored By PubMatic"}},{"id":2,"img":{"type":1,"url":"//sample.com/AdTag/native/728x90.png","w":728,"h":90}},{"id":3,"title":{"text":"Native Test Title"}},{"id":4,"data":{"type":2,"value":"Sponsored By PubMatic"}}],"link":{"url":"//www.sample.com","clicktrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35"],"fallback":"http://www.sample.com"},"imptrackers":["http://sampletracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35","Tracking URL"],"jstracker":"\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e\u003cscript src='\\/\\/sample.com\\/AdTag\\/native\\/tempReseponse.js'\u003e","eventtrackers":[{"event":1,"method":1,"url":"http://sample.com/AdServer/AdDisplayTrackerServlet"}]}`, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := injectNativeImpressionTracker(tt.args.adm, tt.args.tracker) + if (err != nil) != tt.wantErr { + t.Errorf("injectNativeImpressionTracker() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("injectNativeImpressionTracker() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/tracker/tracker.go b/modules/pubmatic/openwrap/tracker/tracker.go new file mode 100644 index 00000000000..ed4a9881ec6 --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/tracker.go @@ -0,0 +1,50 @@ +package tracker + +import ( + "fmt" + "net/url" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func GetTrackerInfo(rCtx models.RequestCtx, responseExt openrtb_ext.ExtBidResponse) string { + floorsDetails := models.GetFloorsDetails(responseExt) + tracker := models.Tracker{ + PubID: rCtx.PubID, + ProfileID: fmt.Sprintf("%d", rCtx.ProfileID), + VersionID: fmt.Sprintf("%d", rCtx.DisplayID), + PageURL: rCtx.PageURL, + Timestamp: rCtx.StartTime, + IID: rCtx.LoggerImpressionID, + Platform: int(rCtx.DevicePlatform), + Origin: rCtx.Origin, + TestGroup: rCtx.ABTestConfigApplied, + FloorModelVersion: floorsDetails.FloorModelVersion, + FloorType: floorsDetails.FloorType, + FloorSkippedFlag: floorsDetails.Skipfloors, + FloorSource: floorsDetails.FloorSource, + } + + constructedURLString := constructTrackerURL(rCtx, tracker) + + trackerURL, err := url.Parse(constructedURLString) + if err != nil { + return "" + } + + params := trackerURL.Query() + params.Set(models.TRKPartnerID, models.MacroPartnerName) + params.Set(models.TRKBidderCode, models.MacroBidderCode) + params.Set(models.TRKKGPV, models.MacroKGPV) + params.Set(models.TRKGrossECPM, models.MacroGrossECPM) + params.Set(models.TRKNetECPM, models.MacroNetECPM) + params.Set(models.TRKBidID, models.MacroBidID) + params.Set(models.TRKOrigBidID, models.MacroOrigBidID) + params.Set(models.TRKSlotID, models.MacroSlotID) + params.Set(models.TRKAdunit, models.MacroAdunit) + params.Set(models.TRKRewardedInventory, models.MacroRewarded) + trackerURL.RawQuery = params.Encode() + + return trackerURL.String() +} diff --git a/modules/pubmatic/openwrap/tracker/tracker_test.go b/modules/pubmatic/openwrap/tracker/tracker_test.go new file mode 100644 index 00000000000..948acf4ab28 --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/tracker_test.go @@ -0,0 +1,87 @@ +package tracker + +import ( + "strconv" + "testing" + "time" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" +) + +func TestGetTrackerInfo(t *testing.T) { + startTime := int64(time.Now().Unix()) + type args struct { + rCtx models.RequestCtx + responseExt openrtb_ext.ExtBidResponse + } + tests := []struct { + name string + args args + want string + }{ + { + name: "all_tracker_info_without_floors", + args: args{ + rCtx: models.RequestCtx{ + TrackerEndpoint: "localhost:8080/wt", + PubID: 123, + ProfileID: 1, + VersionID: 1, + PageURL: "www.test.com", + LoggerImpressionID: "iid123", + StartTime: startTime, + DevicePlatform: models.DevicePlatformMobileAppAndroid, + Origin: "www.publisher.com", + ABTestConfigApplied: 1, + }, + responseExt: openrtb_ext.ExtBidResponse{}, + }, + 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&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), + }, + { + name: "all_tracker_info_with_floors", + args: args{ + rCtx: models.RequestCtx{ + TrackerEndpoint: "localhost:8080/wt", + PubID: 123, + ProfileID: 1, + VersionID: 1, + PageURL: "www.test.com", + LoggerImpressionID: "iid123", + StartTime: startTime, + DevicePlatform: models.DevicePlatformMobileAppAndroid, + Origin: "www.publisher.com", + ABTestConfigApplied: 1, + }, + responseExt: openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + Skipped: ptrutil.ToPtr(true), + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "version 1", + }, + }, + }, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + }, + }, + }, + }, + }, + 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), + }, + } + 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) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/tracker/video.go b/modules/pubmatic/openwrap/tracker/video.go new file mode 100644 index 00000000000..15815b319d7 --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/video.go @@ -0,0 +1,157 @@ +package tracker + +import ( + "errors" + "fmt" + "strings" + + "github.com/beevik/etree" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +// Inject Trackers in Video Creative +func injectVideoCreativeTrackers(bid openrtb2.Bid, videoParams []models.OWTracker) (string, error) { + if bid.AdM == "" || len(videoParams) == 0 { + return "", errors.New("bid is nil or tracker data is missing") + } + + originalCreativeStr := bid.AdM + if strings.HasPrefix(originalCreativeStr, models.HTTPProtocol) { + originalCreativeStr = strings.Replace(models.VastWrapper, models.PartnerURLPlaceholder, originalCreativeStr, -1) + originalCreativeStr = strings.Replace(originalCreativeStr, models.TrackerPlaceholder, videoParams[0].TrackerURL, -1) + originalCreativeStr = strings.Replace(originalCreativeStr, models.ErrorPlaceholder, videoParams[0].ErrorURL, -1) + bid.AdM = originalCreativeStr + } else { + originalCreativeStr = strings.TrimSpace(originalCreativeStr) + doc := etree.NewDocument() + if err := doc.ReadFromString(originalCreativeStr); err != nil { + return bid.AdM, errors.New("invalid creative format") + } + + //Check VAST Object + vast := doc.Element.FindElement(models.VideoVASTTag) + if vast == nil { + return bid.AdM, errors.New("VAST Tag Not Found") + } + + //GetVersion + version := vast.SelectAttrValue(models.VideoVASTVersion, models.VideoVASTVersion2_0) + + adElements := doc.FindElements(models.VASTAdElement) + for i, adElement := range adElements { + if i < len(videoParams) { + element := adElement.FindElement(models.AdWrapperElement) + isWrapper := (nil != element) + + if nil == element { + element = adElement.FindElement(models.AdInlineElement) + } + + if nil == element { + return bid.AdM, errors.New("video creative not in required VAST format") + } + + if len(videoParams[i].TrackerURL) > 0 { + // set tracker URL + newElement := etree.NewElement(models.ImpressionElement) + newElement.SetText(videoParams[i].TrackerURL) + element.InsertChild(element.SelectElement(models.ImpressionElement), newElement) + } + + if len(videoParams[i].ErrorURL) > 0 { + // set error URL + newElement := etree.NewElement(models.ErrorElement) + newElement.SetText(videoParams[i].ErrorURL) + element.InsertChild(element.SelectElement(models.ErrorElement), newElement) + } + + if false == isWrapper && videoParams[i].Price != 0 { + if models.VideoVASTVersion2_0 == version { + injectPricingNodeVAST20(element, videoParams[i].Price, videoParams[i].PriceModel, videoParams[i].PriceCurrency) + } else { + injectPricingNodeVAST3x(element, videoParams[i].Price, videoParams[i].PriceModel, videoParams[i].PriceCurrency) + } + } + } + } + + updatedVastStr, err := doc.WriteToString() + if err != nil { + return bid.AdM, err + } + return updatedVastStr, nil + } + return bid.AdM, nil +} + +func injectPricingNodeVAST20(parent *etree.Element, price float64, model string, currency string) { + extensions := parent.FindElement(models.VideoTagLookupStart + models.VideoExtensionsTag) + if nil == extensions { + extensions = parent.CreateElement(models.VideoExtensionsTag) + } + + pricing := extensions.FindElement(models.VideoVAST2ExtensionPriceElement) + if nil != pricing { + //Already Present Same Node, So Ignore It + updatePricingNode(pricing, price, model, currency) + } else { + extension := extensions.CreateElement(models.VideoExtensionTag) + extension.InsertChild(nil, newPricingNode(price, model, currency)) + } +} + +func injectPricingNodeVAST3x(parent *etree.Element, price float64, model string, currency string) { + //Insert into Wrapper Elements + pricing := parent.FindElement(models.VideoTagLookupStart + models.VideoPricingTag) + if nil != pricing { + //Already Present + updatePricingNode(pricing, price, model, currency) + } else { + parent.InsertChild(nil, newPricingNode(price, model, currency)) + } +} + +func updatePricingNode(node *etree.Element, price float64, model string, currency string) { + //Update Price + + node.SetText(fmt.Sprintf("%v", price)) + + //Update Pricing.Model + if len(model) == 0 { + model = models.VideoPricingModelCPM + } + attrModel := node.SelectAttr(models.VideoPricingModel) + if nil == attrModel { + attrModel = node.CreateAttr(models.VideoPricingModel, model) + } else { + attrModel.Value = model + } + + //Update Pricing.Currency + currencyStr := models.VideoPricingCurrencyUSD + if currency != "" { + currencyStr = currency + } + attrCurrency := node.SelectAttr(models.VideoPricingCurrency) + if nil == attrCurrency { + attrCurrency = node.CreateAttr(models.VideoPricingCurrency, currencyStr) + } else { + attrCurrency.Value = currencyStr + } +} + +func newPricingNode(price float64, model string, currency string) *etree.Element { + pricing := etree.NewElement(models.VideoPricingTag) + pricing.SetText(fmt.Sprintf("%v", price)) + if len(model) == 0 { + model = models.VideoPricingModelCPM + } + pricing.CreateAttr(models.VideoPricingModel, model) + currencyStr := models.VideoPricingCurrencyUSD + if currency != "" { + currencyStr = currency + } + pricing.CreateAttr(models.VideoPricingCurrency, currencyStr) + return pricing +} diff --git a/modules/pubmatic/openwrap/tracker/video_test.go b/modules/pubmatic/openwrap/tracker/video_test.go new file mode 100644 index 00000000000..f2c44883c06 --- /dev/null +++ b/modules/pubmatic/openwrap/tracker/video_test.go @@ -0,0 +1,565 @@ +package tracker + +import ( + "testing" + + "github.com/beevik/etree" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/stretchr/testify/assert" +) + +func Test_injectVideoCreativeTrackers(t *testing.T) { + type args struct { + bid openrtb2.Bid + videoParams []models.OWTracker + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "empty_bid", + args: args{ + bid: openrtb2.Bid{}, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + }, + }, + }, + want: ``, + wantErr: true, + }, + { + name: "nil_bid.adm", + args: args{ + + bid: openrtb2.Bid{}, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + }, + }, + }, + want: ``, + wantErr: true, + }, + { + name: "empty_bid.adm", + args: args{ + + bid: openrtb2.Bid{ + AdM: ``, + }, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + }, + }, + }, + want: ``, + wantErr: true, + }, + { + name: "empty_bid.adm.partner_url", + args: args{ + + bid: openrtb2.Bid{ + AdM: `https://stagingnyc.pubmatic.com:8443/test/pub_vast.xml`, + }, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + }, + }, + }, + want: `PubMatic Wrapper`, + wantErr: false, + }, + { + name: "empty_vast_params", + args: args{ + + bid: openrtb2.Bid{ + AdM: ``, + }, + videoParams: []models.OWTracker{}, + }, + want: ``, + wantErr: true, + }, + { + name: "invalid_vast", + args: args{ + + bid: openrtb2.Bid{ + AdM: `invalid_vast_creative`, + }, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + }, + }, + }, + want: `invalid_vast_creative`, + wantErr: true, + }, + { + name: "no_vast_ad", + args: args{ + + bid: openrtb2.Bid{ + AdM: ``, + }, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + }, + }, + }, + want: ``, + wantErr: true, + }, + { + name: "vast_2.0_inline_pricing", + args: args{ + + bid: openrtb2.Bid{ + AdM: ``, + }, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + Price: 1.2, + }, + }, + }, + want: ``, + wantErr: false, + }, + { + name: "vast_3.0_inline_pricing", + args: args{ + + bid: openrtb2.Bid{ + AdM: ``, + }, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + Price: 1.2, + }, + }, + }, + want: ``, + wantErr: false, + }, + { + name: "inline_vast_3.0", + args: args{ + + bid: openrtb2.Bid{ + AdM: `Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://dsptracker.com/{PSPM}00:00:04https://www.sample.com`, + }, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracker URL`, + ErrorURL: `Error URL`, + Price: 1.2, + }, + }, + }, + want: ``, + wantErr: false, + }, + { + name: "wrapper_vast_2.0", + args: args{ + + bid: openrtb2.Bid{ + AdM: `DSP`, + }, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracker URL`, + ErrorURL: `Error URL`, + Price: 1.2, + }, + }, + }, + want: ``, + wantErr: false, + }, + { + name: "inline_vast_with_no_cdata", + args: args{ + + bid: openrtb2.Bid{ + AdM: `Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1http://172.16.4.213/AdServer/AdDisplayTrackerServlethttps://dsptracker.com/{PSPM}http://172.16.4.213/trackhttps://Errortrack.com00:00:04http://172.16.4.213/trackhttps://www.sample.comhttps://stagingnyc.pubmatic.com:8443/video/Shashank/mediaFileHost/media/mp4-sample-1.mp4]https://stagingnyc.pubmatic.com:8443/video/Shashank/mediaFileHost/media/mp4-sample-2.mp4]`, + }, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracker URL`, + ErrorURL: `Error URL`, + Price: 1.2, + }, + }, + }, + want: ``, + wantErr: false, + }, + { + name: "wrapper_vast_with_no_cdata", + args: args{ + + bid: openrtb2.Bid{ + AdM: `DSPhttps://stagingnyc.pubmatic.com:8443/test/pub_vast.xmlhttps://track.dsp.com/er=[ERRORCODE]/tracker/errorhttps://track.dsp.com?e=impressionhttp://track.dsp.com/tracker/click`, + }, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracker URL`, + ErrorURL: `Error URL`, + Price: 1.2, + }, + }, + }, + want: ``, + wantErr: false, + }, + { + name: "spaces_in_creative_TET-8024", + args: args{ + + bid: openrtb2.Bid{ + AdM: ` `, + }, + videoParams: []models.OWTracker{ + { + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + }, + }, + }, + want: ``, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := injectVideoCreativeTrackers(tt.args.bid, tt.args.videoParams) + if (err != nil) != tt.wantErr { + t.Errorf("injectVideoCreativeTrackers() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("injectVideoCreativeTrackers() = %v, want %v", got, tt.want) + } + }) + } +} + +func getXMLDocument(tag string) *etree.Document { + doc := etree.NewDocument() + err := doc.ReadFromString(tag) + if err != nil { + return nil + } + return doc +} + +func Test_injectPricingNodeVAST20(t *testing.T) { + type args struct { + doc *etree.Document + price float64 + model string + currency string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "pricing_node_missing", + args: args{ + doc: getXMLDocument(``), + price: 4.5, + model: models.VideoPricingModelCPM, + currency: "USD", + }, + want: ``, + }, + { + name: "extensions_present_pricing_node_missing", + args: args{ + doc: getXMLDocument(``), + price: 4.5, + model: models.VideoPricingModelCPM, + currency: "USD", + }, + want: ``, + }, + { + name: "extension_present_pricing_node_missing", + args: args{ + doc: getXMLDocument(``), + price: 4.5, + model: models.VideoPricingModelCPM, + currency: "USD", + }, + want: ``, + }, + { + name: "override_pricing_cpm", + args: args{ + doc: getXMLDocument(`1.23`), + price: 4.5, + model: models.VideoPricingModelCPM, + currency: "USD", + }, + want: ``, + }, + { + name: "override_pricing_cpm_add_currency", + args: args{ + doc: getXMLDocument(`1.23`), + price: 4.5, + model: models.VideoPricingModelCPM, + currency: "USD", + }, + want: ``, + }, + { + name: "override_pricing_cpm_add_attributes", + args: args{ + doc: getXMLDocument(`1.23`), + price: 4.5, + model: models.VideoPricingModelCPM, + currency: "USD", + }, + want: ``, + }, + { + name: "override_pricing_node", + args: args{ + doc: getXMLDocument(`1.23`), + price: 4.5, + model: models.VideoPricingModelCPM, + currency: "USD", + }, + want: ``, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + injectPricingNodeVAST20(&tt.args.doc.Element, tt.args.price, tt.args.model, tt.args.currency) + actual, _ := tt.args.doc.WriteToString() + assert.Equal(t, tt.want, actual) + }) + } +} + +func Test_injectPricingNodeVAST3x(t *testing.T) { + type args struct { + doc *etree.Document + price float64 + model string + currency string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "override_cpm_pricing", + args: args{ + doc: getXMLDocument(`1.23`), + price: 4.5, + model: models.VideoPricingModelCPM, + currency: "USD", + }, + want: ``, + }, + { + name: "override_cpc_pricing", + args: args{ + doc: getXMLDocument(`1.23`), + price: 4.5, + model: models.VideoPricingModelCPM, + currency: "USD", + }, + want: ``, + }, + { + name: "add_currency", + args: args{ + doc: getXMLDocument(`1.23`), + price: 4.5, + model: models.VideoPricingModelCPM, + currency: "USD", + }, + want: ``, + }, + { + name: "add_all_attributes", + args: args{ + doc: getXMLDocument(`1.23`), + price: 4.5, + model: models.VideoPricingModelCPM, + currency: "USD", + }, + want: ``, + }, + { + name: "pricing_node_not_present", + args: args{ + doc: getXMLDocument(``), + price: 4.5, + model: models.VideoPricingModelCPM, + currency: models.VideoPricingCurrencyUSD, + }, + want: ``, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + injectPricingNodeVAST3x(&tt.args.doc.Element, tt.args.price, tt.args.model, tt.args.currency) + actual, _ := tt.args.doc.WriteToString() + assert.Equal(t, tt.want, actual) + }) + } +} + +func Test_updatePricingNode(t *testing.T) { + type args struct { + doc *etree.Document + price float64 + model string + currency string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "overwrite_existing_price", + args: args{ + doc: getXMLDocument(`4.5`), + price: 1.2, + model: models.VideoPricingModelCPM, + currency: models.VideoPricingCurrencyUSD, + }, + want: ``, + }, + { + name: "empty_attributes", + args: args{ + doc: getXMLDocument(`4.5`), + price: 1.2, + model: models.VideoPricingModelCPM, + currency: models.VideoPricingCurrencyUSD, + }, + want: ``, + }, + { + name: "overwrite_model", + args: args{ + doc: getXMLDocument(`4.5`), + price: 1.2, + model: "CPC", + currency: models.VideoPricingCurrencyUSD, + }, + want: ``, + }, + { + name: "overwrite_currency", + args: args{ + doc: getXMLDocument(`4.5`), + price: 1.2, + model: models.VideoPricingModelCPM, + currency: "INR", + }, + want: ``, + }, + { + name: "default_values_attribute", + args: args{ + doc: getXMLDocument(`4.5`), + price: 1.2, + model: "", + currency: "", + }, + want: ``, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + updatePricingNode(tt.args.doc.ChildElements()[0], tt.args.price, tt.args.model, tt.args.currency) + actual, _ := tt.args.doc.WriteToString() + assert.Equal(t, tt.want, actual) + }) + } +} + +func Test_newPricingNode(t *testing.T) { + type args struct { + price float64 + model string + currency string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "node", + args: args{ + price: 1.2, + model: models.VideoPricingModelCPM, + currency: models.VideoPricingCurrencyUSD, + }, + want: ``, + }, + { + name: "empty_currency", + args: args{ + price: 1.2, + model: models.VideoPricingModelCPM, + currency: "", + }, + want: ``, + }, + { + name: "other_currency", + args: args{ + price: 1.2, + model: models.VideoPricingModelCPM, + currency: `INR`, + }, + want: ``, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := newPricingNode(tt.args.price, tt.args.model, tt.args.currency) + doc := etree.NewDocument() + doc.InsertChild(nil, got) + actual, _ := doc.WriteToString() + assert.Equal(t, tt.want, actual) + }) + } +} diff --git a/modules/pubmatic/openwrap/util.go b/modules/pubmatic/openwrap/util.go new file mode 100644 index 00000000000..e67a61b1737 --- /dev/null +++ b/modules/pubmatic/openwrap/util.go @@ -0,0 +1,301 @@ +package openwrap + +import ( + "os" + "regexp" + "strings" + + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adapters" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" +) + +var ( + widthRegEx *regexp.Regexp + heightRegEx *regexp.Regexp + auIDRegEx *regexp.Regexp + divRegEx *regexp.Regexp + + openRTBDeviceOsAndroidRegex *regexp.Regexp + androidUARegex *regexp.Regexp + iosUARegex *regexp.Regexp + openRTBDeviceOsIosRegex *regexp.Regexp + mobileDeviceUARegex *regexp.Regexp + ctvRegex *regexp.Regexp +) + +const test = "_test" + +func init() { + widthRegEx = regexp.MustCompile(models.MACRO_WIDTH) + heightRegEx = regexp.MustCompile(models.MACRO_HEIGHT) + auIDRegEx = regexp.MustCompile(models.MACRO_AD_UNIT_ID) + //auIndexRegEx := regexp.MustCompile(models.MACRO_AD_UNIT_INDEX) + //integerRegEx := regexp.MustCompile(models.MACRO_INTEGER) + divRegEx = regexp.MustCompile(models.MACRO_DIV) + + openRTBDeviceOsAndroidRegex = regexp.MustCompile(models.OpenRTBDeviceOsAndroidRegexPattern) + androidUARegex = regexp.MustCompile(models.AndroidUARegexPattern) + iosUARegex = regexp.MustCompile(models.IosUARegexPattern) + openRTBDeviceOsIosRegex = regexp.MustCompile(models.OpenRTBDeviceOsIosRegexPattern) + mobileDeviceUARegex = regexp.MustCompile(models.MobileDeviceUARegexPattern) + ctvRegex = regexp.MustCompile(models.ConnectedDeviceUARegexPattern) +} + +// rCtx.DevicePlatform = GetDevicePlatform(rCtx.UA, payload.BidRequest, rCtx.Platform, rCtx.PubIDStr, m.metricEngine) +// +// GetDevicePlatform determines the device from which request has been generated +func GetDevicePlatform(rCtx models.RequestCtx, bidRequest *openrtb2.BidRequest) models.DevicePlatform { + userAgentString := rCtx.UA + if bidRequest != nil && bidRequest.Device != nil && len(bidRequest.Device.UA) != 0 { + userAgentString = bidRequest.Device.UA + rCtx.UA = userAgentString + } + + switch rCtx.Platform { + case models.PLATFORM_AMP: + return models.DevicePlatformMobileWeb + + case models.PLATFORM_APP: + //Its mobile; now determine ios or android + var os = "" + if bidRequest != nil && bidRequest.Device != nil && len(bidRequest.Device.OS) != 0 { + os = bidRequest.Device.OS + } + if isIos(os, userAgentString) { + return models.DevicePlatformMobileAppIos + } else if isAndroid(os, userAgentString) { + return models.DevicePlatformMobileAppAndroid + } + + case models.PLATFORM_DISPLAY: + //Its web; now determine mobile or desktop + var deviceType adcom1.DeviceType + if bidRequest != nil && bidRequest.Device != nil && bidRequest.Device.DeviceType != 0 { + deviceType = bidRequest.Device.DeviceType + } + if isMobile(deviceType, userAgentString) { + return models.DevicePlatformMobileWeb + } + return models.DevicePlatformDesktop + + case models.PLATFORM_VIDEO: + var deviceType adcom1.DeviceType + if bidRequest != nil && bidRequest.Device != nil && bidRequest.Device.DeviceType != 0 { + deviceType = bidRequest.Device.DeviceType + } + isCtv := isCTV(userAgentString) + + if deviceType != 0 { + if deviceType == adcom1.DeviceTV || deviceType == adcom1.DeviceConnected || deviceType == adcom1.DeviceSetTopBox { + return models.DevicePlatformConnectedTv + } + } + + if deviceType == 0 && isCtv { + return models.DevicePlatformConnectedTv + } + + if bidRequest != nil && bidRequest.Site != nil { + //Its web; now determine mobile or desktop + if isMobile(bidRequest.Device.DeviceType, userAgentString) { + return models.DevicePlatformMobileWeb + } + return models.DevicePlatformDesktop + } + + if bidRequest != nil && bidRequest.App != nil { + //Its mobile; now determine ios or android + var os = "" + if bidRequest.Device != nil && len(bidRequest.Device.OS) != 0 { + os = bidRequest.Device.OS + } + + if isIos(os, userAgentString) { + return models.DevicePlatformMobileAppIos + } else if isAndroid(os, userAgentString) { + return models.DevicePlatformMobileAppAndroid + } + } + + default: + return models.DevicePlatformNotDefined + + } + + return models.DevicePlatformNotDefined +} + +func isMobile(deviceType adcom1.DeviceType, userAgentString string) bool { + if deviceType != 0 { + return deviceType == adcom1.DeviceMobile || deviceType == adcom1.DeviceTablet || deviceType == adcom1.DevicePhone + } + + if mobileDeviceUARegex.Match([]byte(strings.ToLower(userAgentString))) { + return true + } + return false +} + +func isIos(os string, userAgentString string) bool { + if openRTBDeviceOsIosRegex.Match([]byte(strings.ToLower(os))) || iosUARegex.Match([]byte(strings.ToLower(userAgentString))) { + return true + } + return false +} + +func isAndroid(os string, userAgentString string) bool { + if openRTBDeviceOsAndroidRegex.Match([]byte(strings.ToLower(os))) || androidUARegex.Match([]byte(strings.ToLower(userAgentString))) { + return true + } + return false +} + +// GetIntArray converts interface to int array if it is compatible +func GetIntArray(val interface{}) []int { + intArray := make([]int, 0) + valArray, ok := val.([]interface{}) + if !ok { + return nil + } + for _, x := range valArray { + var intVal int + intVal = GetInt(x) + intArray = append(intArray, intVal) + } + return intArray +} + +// GetInt converts interface to int if it is compatible +func GetInt(val interface{}) int { + var result int + if val != nil { + switch val.(type) { + case int: + result = val.(int) + case float64: + val := val.(float64) + result = int(val) + case float32: + val := val.(float32) + result = int(val) + } + } + return result +} + +func getSourceAndOrigin(bidRequest *openrtb2.BidRequest) (string, string) { + var source, origin string + if bidRequest.Site != nil { + if len(bidRequest.Site.Domain) != 0 { + source = bidRequest.Site.Domain + origin = source + } else if len(bidRequest.Site.Page) != 0 { + source = getDomainFromUrl(bidRequest.Site.Page) + origin = source + + } + } else if bidRequest.App != nil { + source = bidRequest.App.Bundle + origin = source + } + return source, origin +} + +// getHostName Generates server name from node and pod name in K8S environment +func GetHostName() string { + var ( + nodeName string + podName string + ) + + if nodeName, _ = os.LookupEnv(models.ENV_VAR_NODE_NAME); nodeName == "" { + nodeName = models.DEFAULT_NODENAME + } else { + nodeName = strings.Split(nodeName, ".")[0] + } + + if podName, _ = os.LookupEnv(models.ENV_VAR_POD_NAME); podName == "" { + podName = models.DEFAULT_PODNAME + } else { + podName = strings.TrimPrefix(podName, "ssheaderbidding-") + } + + serverName := nodeName + ":" + podName + + return serverName +} + +// RecordPublisherPartnerNoCookieStats parse request cookies and records the stats if cookie is not found for partner +func RecordPublisherPartnerNoCookieStats(rctx models.RequestCtx) { + + for _, partnerConfig := range rctx.PartnerConfigMap { + if partnerConfig[models.SERVER_SIDE_FLAG] == "0" { + continue + } + + partnerName := partnerConfig[models.PREBID_PARTNER_NAME] + syncer := models.SyncerMap[adapters.ResolveOWBidder(partnerName)] + if syncer != nil { + uid, _, _ := rctx.ParsedUidCookie.GetUID(syncer.Key()) + if uid != "" { + continue + } + } + rctx.MetricsEngine.RecordPublisherPartnerNoCookieStats(rctx.PubIDStr, partnerConfig[models.BidderCode]) + } +} + +// getPubmaticErrorCode is temporary function which returns the pubmatic specific error code for standardNBR code +func getPubmaticErrorCode(standardNBR int) int { + switch standardNBR { + case nbr.InvalidPublisherID: + return 604 // ErrMissingPublisherID + + case nbr.InvalidRequestExt: + return 18 // ErrBadRequest + + case nbr.InvalidProfileID: + return 700 // ErrMissingProfileID + + case nbr.AllPartnerThrottled: + return 11 // ErrAllPartnerThrottled + + case nbr.InvalidPriceGranularityConfig: + return 26 // ErrPrebidInvalidCustomPriceGranularity + + case nbr.InvalidImpressionTagID: + return 605 // ErrMissingTagID + + case nbr.InvalidProfileConfiguration, nbr.InvalidPlatform, nbr.AllSlotsDisabled, nbr.ServerSidePartnerNotConfigured: + return 6 // ErrInvalidConfiguration + + case nbr.InternalError: + return 17 // ErrInvalidImpression + + } + + return -1 +} + +func isCTV(userAgent string) bool { + return ctvRegex.Match([]byte(userAgent)) +} + +func getPlatformFromRequest(request *openrtb2.BidRequest) string { + var platform string + if request.Site != nil { + return models.PLATFORM_DISPLAY + } + if request.App != nil { + return models.PLATFORM_APP + } + return platform +} + +func GetNonBidStatusCodePtr(nbr openrtb3.NonBidStatusCode) *openrtb3.NonBidStatusCode { + return &nbr +} diff --git a/modules/pubmatic/openwrap/util_test.go b/modules/pubmatic/openwrap/util_test.go new file mode 100644 index 00000000000..3207029b9cc --- /dev/null +++ b/modules/pubmatic/openwrap/util_test.go @@ -0,0 +1,947 @@ +package openwrap + +import ( + "net/http" + "os" + "testing" + + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/macros" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/stretchr/testify/assert" +) + +func TestRecordPublisherPartnerNoCookieStats(t *testing.T) { + + ctrl := gomock.NewController(t) + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + defer ctrl.Finish() + + type args struct { + rctx models.RequestCtx + } + + tests := []struct { + name string + args args + getHttpRequest func() *http.Request + setup func(*mock_metrics.MockMetricsEngine) + }{ + { + name: "Empty cookies and empty partner config map", + args: args{ + rctx: models.RequestCtx{}, + }, + setup: func(mme *mock_metrics.MockMetricsEngine) {}, + getHttpRequest: func() *http.Request { + req, _ := http.NewRequest("GET", "http://anyurl.com", nil) + return req + }, + }, + { + name: "Empty cookie and non-empty partner config map", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.SERVER_SIDE_FLAG: "1", + models.PREBID_PARTNER_NAME: "partner1", + models.BidderCode: "bidder1", + }, + }, + PubIDStr: "5890", + }, + }, + setup: func(mme *mock_metrics.MockMetricsEngine) { + models.SyncerMap = make(map[string]usersync.Syncer) + mme.EXPECT().RecordPublisherPartnerNoCookieStats("5890", "bidder1") + }, + getHttpRequest: func() *http.Request { + req, _ := http.NewRequest("GET", "http://anyurl.com", nil) + return req + }, + }, + { + name: "only client side partner in config map", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.SERVER_SIDE_FLAG: "0", + models.PREBID_PARTNER_NAME: "partner1", + models.BidderCode: "bidder1", + }, + }, + PubIDStr: "5890", + }, + }, + setup: func(mme *mock_metrics.MockMetricsEngine) { + models.SyncerMap = make(map[string]usersync.Syncer) + }, + getHttpRequest: func() *http.Request { + req, _ := http.NewRequest("GET", "http://anyurl.com", nil) + return req + }, + }, + { + name: "GetUID returns empty uid", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.SERVER_SIDE_FLAG: "1", + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + }, + }, + PubIDStr: "5890", + }, + }, + setup: func(mme *mock_metrics.MockMetricsEngine) { + models.SyncerMap = map[string]usersync.Syncer{ + "pubmatic": fakeSyncer{ + key: "pubmatic", + }, + } + mme.EXPECT().RecordPublisherPartnerNoCookieStats("5890", "pubmatic") + }, + getHttpRequest: func() *http.Request { + req, _ := http.NewRequest("GET", "http://anyurl.com", nil) + return req + }, + }, + { + name: "GetUID returns non empty uid", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.SERVER_SIDE_FLAG: "1", + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + }, + }, + PubIDStr: "5890", + }, + }, + setup: func(mme *mock_metrics.MockMetricsEngine) { + models.SyncerMap = map[string]usersync.Syncer{ + "pubmatic": fakeSyncer{ + key: "pubmatic", + }, + } + }, + getHttpRequest: func() *http.Request { + req, _ := http.NewRequest("GET", "http://anyurl.com", nil) + + cookie := &http.Cookie{ + Name: "uids", + Value: "ewoJInRlbXBVSURzIjogewoJCSJwdWJtYXRpYyI6IHsKCQkJInVpZCI6ICI3RDc1RDI1Ri1GQUM5LTQ0M0QtQjJEMS1CMTdGRUUxMUUwMjciLAoJCQkiZXhwaXJlcyI6ICIyMDIyLTEwLTMxVDA5OjE0OjI1LjczNzI1Njg5OVoiCgkJfQoJfSwKCSJiZGF5IjogIjIwMjItMDUtMTdUMDY6NDg6MzguMDE3OTg4MjA2WiIKfQ==", + } + req.AddCookie(cookie) + return req + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup(mockEngine) + tc.args.rctx.MetricsEngine = mockEngine + tc.args.rctx.ParsedUidCookie = usersync.ReadCookie(tc.getHttpRequest(), usersync.Base64Decoder{}, &config.HostCookie{}) + RecordPublisherPartnerNoCookieStats(tc.args.rctx) + }) + } +} + +// fakeSyncer implements syncer interface for unit test cases +type fakeSyncer struct { + key string +} + +func (s fakeSyncer) Key() string { + return s.key +} + +func (s fakeSyncer) DefaultSyncType() usersync.SyncType { + return usersync.SyncType("") +} + +func (s fakeSyncer) SupportsType(syncTypes []usersync.SyncType) bool { + return false +} + +func (fakeSyncer) GetSync([]usersync.SyncType, macros.UserSyncPrivacy) (usersync.Sync, error) { + return usersync.Sync{}, nil +} + +func TestGetDevicePlatform(t *testing.T) { + type args struct { + rCtx models.RequestCtx + bidRequest *openrtb2.BidRequest + } + tests := []struct { + name string + args args + want models.DevicePlatform + }{ + { + name: "Test_empty_platform", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "", + }, + bidRequest: nil, + }, + want: models.DevicePlatformNotDefined, + }, + { + name: "Test_platform_amp", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "amp", + }, + bidRequest: nil, + }, + want: models.DevicePlatformMobileWeb, + }, + { + name: "Test_platform_in-app_with_iOS_UA", + args: args{ + rCtx: models.RequestCtx{ + UA: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + Platform: "in-app", + }, + bidRequest: nil, + }, + want: models.DevicePlatformMobileAppIos, + }, + { + name: "Test_platform_in-app_with_Android_UA", + args: args{ + rCtx: models.RequestCtx{ + UA: "Mozilla/5.0 (Linux; Android 7.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Focus/1.0 Chrome/59.0.3029.83 Mobile Safari/537.36", + Platform: "in-app", + }, + bidRequest: nil, + }, + want: models.DevicePlatformMobileAppAndroid, + }, + { + name: "Test_platform_in-app_with_device.os_android", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "in-app", + }, + bidRequest: getORTBRequest("android", "", 0, false, true), + }, + want: models.DevicePlatformMobileAppAndroid, + }, + { + name: "Test_platform_in-app_with_device.os_ios", + args: args{ + rCtx: models.RequestCtx{ + UA: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + Platform: "in-app", + }, + bidRequest: getORTBRequest("ios", "", 0, false, true), + }, + want: models.DevicePlatformMobileAppIos, + }, + { + name: "Test_platform_in-app_with_device.ua_for_ios", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "in-app", + }, + bidRequest: getORTBRequest("", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", 0, false, true), + }, + want: models.DevicePlatformMobileAppIos, + }, + { + name: "Test_platform_display_with_device.deviceType_for_mobile", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "display", + }, + bidRequest: getORTBRequest("", "", adcom1.DeviceMobile, false, true), + }, + want: models.DevicePlatformMobileWeb, + }, + { + name: "Test_platform_display_with_device.deviceType_for_tablet", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "display", + }, + bidRequest: getORTBRequest("", "", adcom1.DeviceMobile, false, true), + }, + want: models.DevicePlatformMobileWeb, + }, + { + name: "Test_platform_display_with_device.deviceType_for_desktop", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "display", + }, + bidRequest: getORTBRequest("", "", adcom1.DevicePC, true, false), + }, + want: models.DevicePlatformDesktop, + }, + { + name: "Test_platform_display_with_device.ua_for_mobile", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "display", + }, + bidRequest: getORTBRequest("", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", 0, true, false), + }, + want: models.DevicePlatformMobileWeb, + }, + { + name: "Test_platform_display_without_ua,_os_&_deviceType", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "display", + }, + bidRequest: getORTBRequest("", "", 0, false, true), + }, + want: models.DevicePlatformDesktop, + }, + { + name: "Test_platform_video_with_deviceType_as_CTV", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + PubIDStr: "5890", + }, + bidRequest: getORTBRequest("", "", adcom1.DeviceTV, true, false), + }, + want: models.DevicePlatformConnectedTv, + }, + { + name: "Test_platform_video_with_deviceType_as_connected_device", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + PubIDStr: "5890", + }, + bidRequest: getORTBRequest("", "", adcom1.DeviceConnected, true, false), + }, + want: models.DevicePlatformConnectedTv, + }, + { + name: "Test_platform_video_with_deviceType_as_set_top_box", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + PubIDStr: "5890", + }, + bidRequest: getORTBRequest("", "", adcom1.DeviceSetTopBox, false, true), + }, + want: models.DevicePlatformConnectedTv, + }, + { + name: "Test_platform_video_with_nil_values", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + }, + bidRequest: getORTBRequest("", "", 0, true, false), + }, + want: models.DevicePlatformDesktop, + }, + { + name: "Test_platform_video_with_site_entry", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + }, + bidRequest: getORTBRequest("", "", 0, true, false), + }, + want: models.DevicePlatformDesktop, + }, + { + name: "Test_platform_video_with_site_entry_and_mobile_UA", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + }, + bidRequest: getORTBRequest("", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", 0, true, false), + }, + want: models.DevicePlatformMobileWeb, + }, + { + name: "Test_platform_video_with_app_entry_and_iOS_mobile_UA", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + }, + bidRequest: getORTBRequest("", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", 0, false, true), + }, + want: models.DevicePlatformMobileAppIos, + }, + { + name: "Test_platform_video_with_app_entry_and_android_mobile_UA", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + }, + + bidRequest: getORTBRequest("", "Mozilla/5.0 (Linux; Android 7.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Focus/1.0 Chrome/59.0.3029.83 Mobile Safari/537.36", 0, false, true), + }, + want: models.DevicePlatformMobileAppAndroid, + }, + { + name: "Test_platform_video_with_app_entry_and_android_os", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + }, + bidRequest: getORTBRequest("android", "", 0, false, true), + }, + want: models.DevicePlatformMobileAppAndroid, + }, + { + name: "Test_platform_video_with_app_entry_and_ios_os", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + }, + bidRequest: getORTBRequest("ios", "", 0, false, true), + }, + want: models.DevicePlatformMobileAppIos, + }, + { + name: "Test_platform_video_with_CTV_and_device_type", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + PubIDStr: "5890", + }, + bidRequest: getORTBRequest("", "Mozilla/5.0 (SMART-TV; Linux; Tizen 4.0) AppleWebKit/538.1 (KHTML, like Gecko) Version/4.0 TV Safari/538.1", 3, false, true), + }, + want: models.DevicePlatformConnectedTv, + }, + { + name: "Test_platform_video_with_CTV_and_no_device_type", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + }, + bidRequest: getORTBRequest("", "AppleCoreMedia/1.0.0.20L498 (Apple TV; U; CPU OS 16_4_1 like Mac OS X; en_us)", 0, true, false), + }, + want: models.DevicePlatformConnectedTv, + }, + { + name: "Test_platform_video_for_non_CTV_User_agent_with_device_type_7", + args: args{ + rCtx: models.RequestCtx{ + UA: "", + Platform: "video", + PubIDStr: "5890", + }, + bidRequest: getORTBRequest("", "AppleCoreMedia/1.0.0.20L498 (iphone ; U; CPU OS 16_4_1 like Mac OS X; en_us)", 7, false, true), + }, + want: models.DevicePlatformConnectedTv, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GetDevicePlatform(tt.args.rCtx, tt.args.bidRequest) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestIsMobile(t *testing.T) { + type args struct { + deviceType adcom1.DeviceType + userAgentString string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Test_for_deviceType_1", + args: args{ + deviceType: adcom1.DeviceMobile, + userAgentString: "", + }, + want: true, + }, + { + name: "Test_for_deviceType_2", + args: args{ + deviceType: adcom1.DevicePC, + userAgentString: "", + }, + want: false, + }, + { + name: "Test_for_deviceType_4:_phone", + args: args{ + deviceType: adcom1.DevicePhone, + userAgentString: "", + }, + want: true, + }, + { + name: "Test_for_deviceType_5:_tablet", + args: args{ + deviceType: adcom1.DeviceTablet, + userAgentString: "", + }, + want: true, + }, + { + name: "Test_for_iPad_User-Agent", + args: args{ + deviceType: 0, + userAgentString: "Mozilla/5.0 (iPad; CPU OS 10_3_3 like Mac OS X) AppleWebKit/603.3.8 (KHTML, like Gecko) Mobile/14G60", + }, + want: true, + }, + { + name: "Test_for_iPhone_User-Agent", + args: args{ + deviceType: 0, + userAgentString: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + }, + want: true, + }, + { + name: "Test_for_Safari_web-browser_on_mobile_User-Agent", + args: args{ + deviceType: 0, + userAgentString: "MobileSafari/602.1 CFNetwork/811.5.4 Darwin/16.7.0", + }, + want: true, + }, + { + name: "Test_for_Outlook_3_application_on_mobile_phone_User-Agent", + args: args{ + deviceType: 0, + userAgentString: "Outlook-iOS/709.2144270.prod.iphone (3.23.0)", + }, + want: true, + }, + { + name: "Test_for_firefox_11_tablet_User-Agent", + args: args{ + deviceType: 0, + userAgentString: "Mozilla/5.0 (Android 4.4; Tablet; rv:41.0) Gecko/41.0 Firefox/41.0", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isMobile(tt.args.deviceType, tt.args.userAgentString) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestIsIos(t *testing.T) { + type args struct { + os string + userAgentString string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "iOS_test_for_Web_Browser_Mobile-Tablet", + args: args{ + os: "", + userAgentString: "Mozilla/5.0 (iPad; CPU OS 10_3_3 like Mac OS X) AppleWebKit/603.3.8 (KHTML, like Gecko) Mobile/14G60", + }, + want: true, + }, + { + name: "iOS_test_for_Safari_13_Mobile-Phone", + args: args{ + os: "", + userAgentString: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + }, + want: true, + }, + { + name: "Test_for_Safari_web-browser_on_mobile_User-Agent", + args: args{ + os: "", + userAgentString: "MobileSafari/602.1 CFNetwork/811.5.4 Darwin/16.7.0", + }, + want: true, + }, + { + name: "Test_for_iPhone_XR_simulator_User-Agent", + args: args{ + os: "", + userAgentString: "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/7.0.4 Mobile/16B91 Safari/605.1.15", + }, + want: true, + }, + { + name: "Test_for_Outlook_3_Application_User-Agent", + args: args{ + os: "", + userAgentString: "Outlook-iOS/709.2144270.prod.iphone (3.23.0)", + }, + want: true, + }, + { + name: "iOS_test_for_Safari_12_Mobile-Phone", + args: args{ + os: "", + userAgentString: "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isIos(tt.args.os, tt.args.userAgentString) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestIsAndroid(t *testing.T) { + type args struct { + os string + userAgentString string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Test_android_with_correct_os_value", + args: args{ + os: "android", + userAgentString: "", + }, + want: true, + }, + { + name: "Test_android_with_invalid_os_value", + args: args{ + os: "ios", + userAgentString: "", + }, + want: false, + }, + { + name: "Test_android_with_invalid_osv_alue", + args: args{ + os: "", + userAgentString: "", + }, + want: false, + }, + { + name: "Test_android_with_UA_value", + args: args{ + os: "", + userAgentString: "Mozilla/5.0 (Linux; Android 7.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Focus/1.0 Chrome/59.0.3029.83 Mobile Safari/537.36", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isAndroid(tt.args.os, tt.args.userAgentString) + assert.Equal(t, tt.want, got) + }) + } +} + +func getORTBRequest(os, ua string, deviceType adcom1.DeviceType, withSite, withApp bool) *openrtb2.BidRequest { + request := new(openrtb2.BidRequest) + + if withSite { + request.Site = &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + } + } + + if withApp { + request.App = &openrtb2.App{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + } + } + + request.Device = new(openrtb2.Device) + request.Device.UA = ua + + request.Device.OS = os + + request.Device.DeviceType = deviceType + + return request +} + +func TestGetSourceAndOrigin(t *testing.T) { + type args struct { + bidRequest *openrtb2.BidRequest + } + type want struct { + source string + origin string + } + tests := []struct { + name string + args args + want want + }{ + { + name: "bidRequest_site_conatins_Domain", + args: args{ + bidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Page: "http://www.test.com", + Domain: "test.com", + }, + }, + }, + want: want{ + source: "test.com", + origin: "test.com", + }, + }, + { + name: "bidRequest_conatins_Site_Page", + args: args{ + bidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Page: "http://www.test.com", + }, + }, + }, + want: want{ + source: "www.test.com", + origin: "www.test.com", + }, + }, + { + name: "bidRequest_conatins_App_Bundle", + args: args{ + bidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{ + Bundle: "com.pub.test", + }, + }, + }, + want: want{ + source: "com.pub.test", + origin: "com.pub.test", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + source, origin := getSourceAndOrigin(tt.args.bidRequest) + assert.Equal(t, tt.want.source, source) + assert.Equal(t, tt.want.origin, origin) + }) + } +} + +func TestGetHostName(t *testing.T) { + var ( + node string + pod string + ) + + saveEnvVarsForServerName := func() { + node, _ = os.LookupEnv(models.ENV_VAR_NODE_NAME) + pod, _ = os.LookupEnv(models.ENV_VAR_POD_NAME) + } + + resetEnvVarsForServerName := func() { + os.Setenv(models.ENV_VAR_NODE_NAME, node) + os.Setenv(models.ENV_VAR_POD_NAME, pod) + } + type args struct { + nodeName string + podName string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "default_value", + args: args{}, + want: models.DEFAULT_NODENAME + ":" + models.DEFAULT_PODNAME, + }, + { + name: "valid_name", + args: args{ + nodeName: "sfo2hyp084.sfo2.pubmatic.com", + podName: "ssheaderbidding-0-0-38-pr-26-2-k8s-5679748b7b-tqh42", + }, + want: "sfo2hyp084:0-0-38-pr-26-2-k8s-5679748b7b-tqh42", + }, + { + name: "special_characters", + args: args{ + nodeName: "sfo2hyp084.sfo2.pubmatic.com!!!@#$-_^%x090", + podName: "ssheaderbidding-0-0-38-pr-26-2-k8s-5679748b7b-tqh42", + }, + want: "sfo2hyp084:0-0-38-pr-26-2-k8s-5679748b7b-tqh42", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + saveEnvVarsForServerName() + + if len(tt.args.nodeName) > 0 { + os.Setenv(models.ENV_VAR_NODE_NAME, tt.args.nodeName) + } + + if len(tt.args.podName) > 0 { + os.Setenv(models.ENV_VAR_POD_NAME, tt.args.podName) + } + + got := GetHostName() + assert.Equal(t, tt.want, got) + + resetEnvVarsForServerName() + }) + } +} + +func TestGetPubmaticErrorCode(t *testing.T) { + type args struct { + standardNBR int + } + tests := []struct { + name string + args args + want int + }{ + { + name: "ErrMissingPublisherID", + args: args{ + standardNBR: nbr.InvalidPublisherID, + }, + want: 604, + }, + { + name: "ErrBadRequest", + args: args{ + standardNBR: nbr.InvalidRequestExt, + }, + want: 18, + }, + { + name: "ErrMissingProfileID", + args: args{ + standardNBR: nbr.InvalidProfileID, + }, + want: 700, + }, + { + name: "ErrAllPartnerThrottled", + args: args{ + standardNBR: nbr.AllPartnerThrottled, + }, + want: 11, + }, + { + name: "ErrPrebidInvalidCustomPriceGranularity", + args: args{ + standardNBR: nbr.InvalidPriceGranularityConfig, + }, + want: 26, + }, + { + name: "ErrMissingTagID", + args: args{ + standardNBR: nbr.InvalidImpressionTagID, + }, + want: 605, + }, + { + name: "ErrInvalidConfiguration", + args: args{ + standardNBR: nbr.InvalidProfileConfiguration, + }, + want: 6, + }, + { + name: "ErrInvalidConfiguration_platform", + args: args{ + standardNBR: nbr.InvalidPlatform, + }, + want: 6, + }, + { + name: "ErrInvalidConfiguration_AllSlotsDisabled", + args: args{ + standardNBR: nbr.AllSlotsDisabled, + }, + want: 6, + }, + { + name: "ErrInvalidConfiguration_ServerSidePartnerNotConfigured", + args: args{ + standardNBR: nbr.ServerSidePartnerNotConfigured, + }, + want: 6, + }, + { + name: "ErrInvalidImpression", + args: args{ + standardNBR: nbr.InternalError, + }, + want: 17, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getPubmaticErrorCode(tt.args.standardNBR) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/utils/bid.go b/modules/pubmatic/openwrap/utils/bid.go new file mode 100644 index 00000000000..c28d85a769f --- /dev/null +++ b/modules/pubmatic/openwrap/utils/bid.go @@ -0,0 +1,17 @@ +package utils + +import ( + "regexp" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +var bidIDRegx = regexp.MustCompile("(" + models.BidIdSeparator + ")") + +func GetOriginalBidId(bidID string) string { + return bidIDRegx.Split(bidID, -1)[0] +} + +func SetUniqueBidID(originalBidID, generatedBidID string) string { + return originalBidID + models.BidIdSeparator + generatedBidID +} diff --git a/modules/pubmatic/openwrap/utils/bid_test.go b/modules/pubmatic/openwrap/utils/bid_test.go new file mode 100644 index 00000000000..d115fe1d1be --- /dev/null +++ b/modules/pubmatic/openwrap/utils/bid_test.go @@ -0,0 +1,125 @@ +package utils + +import ( + "testing" +) + +func TestGetOriginalBidId(t *testing.T) { + type args struct { + bidId string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Split the bid Id when get valid bidId", + args: args{ + bidId: "original-id::gen-id", + }, + want: "original-id", + }, + { + name: "Empty BidId", + args: args{ + bidId: "", + }, + want: "", + }, + { + name: "Partial BidId", + args: args{ + bidId: "::gen-id", + }, + want: "", + }, + { + name: "Partial BidId without generated and separator", + args: args{ + bidId: "original-bid-1", + }, + want: "original-bid-1", + }, + { + name: "Partial BidId without generated", + args: args{ + bidId: "original-bid::", + }, + want: "original-bid", + }, + { + name: "BidId with single colon in origin Id", + args: args{ + bidId: "original-bid:2::generated-bid", + }, + want: "original-bid:2", + }, + { + name: "BidId with single colon in generated Id", + args: args{ + bidId: "original-bid:2::generated-bid:3", + }, + want: "original-bid:2", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetOriginalBidId(tt.args.bidId); got != tt.want { + t.Errorf("GetOriginalBidId() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSetUniqueBidID(t *testing.T) { + type args struct { + originalBidID string + generatedBidID string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Unique bid id will be generated", + args: args{ + originalBidID: "orig-bid", + generatedBidID: "gen-bid", + }, + want: "orig-bid::gen-bid", + }, + { + name: "Original Bid Id empty", + args: args{ + originalBidID: "", + generatedBidID: "gen-bid", + }, + want: "::gen-bid", + }, + { + name: "generated BidId empty", + args: args{ + originalBidID: "orig-bid", + generatedBidID: "", + }, + want: "orig-bid::", + }, + { + name: "Both Id empty", + args: args{ + originalBidID: "", + generatedBidID: "", + }, + want: "::", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := SetUniqueBidID(tt.args.originalBidID, tt.args.generatedBidID); got != tt.want { + t.Errorf("SetUniqueBidId() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/modules/pubmatic/vastunwrap/README.md b/modules/pubmatic/vastunwrap/README.md new file mode 100644 index 00000000000..c1f9947a533 --- /dev/null +++ b/modules/pubmatic/vastunwrap/README.md @@ -0,0 +1,6 @@ +# Overview + +The main aim of this module is to provide vast unwrapping feature for CTV endpoint. It has 2 main functionality + - Entryhook : Entryhook will take request payload and check for vast unwrapper enable flag in the request context and update the same in module context as rctx. + - raw bidder response : This stage will use vast unwrapper enable flag from module context and decide whether to use vast unwrapping or not + diff --git a/modules/pubmatic/vastunwrap/constant.go b/modules/pubmatic/vastunwrap/constant.go new file mode 100644 index 00000000000..d850198dc72 --- /dev/null +++ b/modules/pubmatic/vastunwrap/constant.go @@ -0,0 +1,17 @@ +package vastunwrap + +const ( + VastUnwrapEnabled = "enableVastUnwrapper" + RequestContext = "rctx" + UserAgent = "User-Agent" + UnwrapCount = "unwrap-count" + UnwrapStatus = "unwrap-status" + Success = "Success" + Failure = "Failure" + Timeout = "Timeout" + UnwrapStatusTimeout = "2" + UnwrapURL = "http://localhost:8003/unwrap" + ContentType = "Content-Type" + UnwrapTimeout = "unwrap-timeout" + MediaTypeVideo = "video" +) diff --git a/modules/pubmatic/vastunwrap/entryhook.go b/modules/pubmatic/vastunwrap/entryhook.go new file mode 100644 index 00000000000..e8e843836e8 --- /dev/null +++ b/modules/pubmatic/vastunwrap/entryhook.go @@ -0,0 +1,34 @@ +package vastunwrap + +import ( + "context" + "runtime/debug" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/pubmatic/vastunwrap/models" +) + +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, +) (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.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 new file mode 100644 index 00000000000..3b48a3e5635 --- /dev/null +++ b/modules/pubmatic/vastunwrap/entryhook_test.go @@ -0,0 +1,67 @@ +package vastunwrap + +import ( + "context" + "net/http" + "reflect" + "testing" + + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/pubmatic/vastunwrap/models" +) + +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: "Disable Vast Unwrapper", + args: args{ + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + ctx := context.WithValue(context.Background(), VastUnwrapEnabled, "0") + r, _ := http.NewRequestWithContext(ctx, "", "", nil) + return r + }(), + }, + config: VastUnwrapModule{ + TrafficPercentage: 2, + Enabled: false, + }, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false}}}, + }, + { + name: "Enable Vast Unwrapper", + args: args{ + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + ctx := context.WithValue(context.Background(), VastUnwrapEnabled, "1") + r, _ := http.NewRequestWithContext(ctx, "", "", nil) + return r + }(), + }, + config: VastUnwrapModule{ + TrafficPercentage: 2, + }, + }, + randomNum: 1, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + }, + } + 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) + } + }) + } +} diff --git a/modules/pubmatic/vastunwrap/hook_raw_bidder_response.go b/modules/pubmatic/vastunwrap/hook_raw_bidder_response.go new file mode 100644 index 00000000000..e0eaff2a92b --- /dev/null +++ b/modules/pubmatic/vastunwrap/hook_raw_bidder_response.go @@ -0,0 +1,44 @@ +package vastunwrap + +import ( + "sync" + + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/modules/pubmatic/vastunwrap/models" + + "github.com/prebid/prebid-server/v2/hooks/hookstage" +) + +func (m VastUnwrapModule) handleRawBidderResponseHook( + miCtx hookstage.ModuleInvocationContext, + payload hookstage.RawBidderResponsePayload, + unwrapURL string, +) (result hookstage.HookResult[hookstage.RawBidderResponsePayload], err error) { + vastRequestContext, ok := miCtx.ModuleContext[RequestContext].(models.RequestCtx) + if !ok { + result.DebugMessages = append(result.DebugMessages, "error: request-ctx not found in handleRawBidderResponseHook()") + return result, nil + } + if !vastRequestContext.VastUnwrapEnabled { + result.DebugMessages = append(result.DebugMessages, "error: vast unwrap flag is not enabled in handleRawBidderResponseHook()") + return result, nil + } + defer func() { + miCtx.ModuleContext[RequestContext] = vastRequestContext + }() + wg := new(sync.WaitGroup) + for _, bid := range payload.Bids { + if string(bid.BidType) == MediaTypeVideo { + wg.Add(1) + go func(bid *adapters.TypedBid) { + defer wg.Done() + m.doUnwrapandUpdateBid(bid, vastRequestContext.UA, unwrapURL, miCtx.AccountID, payload.Bidder) + }(bid) + } + } + wg.Wait() + changeSet := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} + changeSet.RawBidderResponse().Bids().Update(payload.Bids) + result.ChangeSet = changeSet + 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 new file mode 100644 index 00000000000..4daa3782b8d --- /dev/null +++ b/modules/pubmatic/vastunwrap/hook_raw_bidder_response_test.go @@ -0,0 +1,292 @@ +package vastunwrap + +import ( + "fmt" + "net/http" + + "testing" + + "git.pubmatic.com/vastunwrap/config" + unWrapCfg "git.pubmatic.com/vastunwrap/config" + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/pubmatic/vastunwrap/models" + mock_stats "github.com/prebid/prebid-server/v2/modules/pubmatic/vastunwrap/stats/mock" + "github.com/stretchr/testify/assert" +) + +func TestHandleRawBidderResponseHook(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + 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 + } + 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: "Empty Request Context", + args: args{ + module: VastUnWrapModule, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{DebugMessages: []string{"error: request-ctx not found in handleRawBidderResponseHook()"}}, + wantErr: false, + }, + { + name: "Set Vast Unwrapper to false in request context with type video", + 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: "
This is an Ad
", + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }}}, + moduleInvocationCtx: hookstage.ModuleInvocationContext{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false}}}, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + wantErr: false, + }, + { + name: "Set Vast Unwrapper to true in request context with invalid vast xml", + 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: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + url: UnwrapURL, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + setup: func() { + mockMetricsEngine.EXPECT().RecordRequestStatus("pubmatic", "1").AnyTimes() + mockMetricsEngine.EXPECT().RecordRequestTime("pubmatic", gomock.Any()).AnyTimes() + }, + unwrapRequest: func(w http.ResponseWriter, req *http.Request) { + w.Header().Add("unwrap-status", "1") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(invalidVastXMLAdM)) + }, + wantErr: true, + }, + { + name: "Set Vast Unwrapper to true in request context with type video", + 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{VastUnwrapEnabled: true}}}, + url: UnwrapURL, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + setup: func() { + mockMetricsEngine.EXPECT().RecordRequestStatus("pubmatic", "0").AnyTimes() + mockMetricsEngine.EXPECT().RecordWrapperCount("pubmatic", "1").AnyTimes() + mockMetricsEngine.EXPECT().RecordRequestTime("pubmatic", 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)) + }, + wantErr: false, + }, + { + name: "Set Vast Unwrapper to true in request context for multiple bids with type video", + 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", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "video", + }}, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + url: UnwrapURL, + wantAdM: true, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + setup: func() { + mockMetricsEngine.EXPECT().RecordRequestStatus("pubmatic", "0").AnyTimes() + mockMetricsEngine.EXPECT().RecordWrapperCount("pubmatic", "1").AnyTimes() + mockMetricsEngine.EXPECT().RecordRequestTime("pubmatic", 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)) + }, + wantErr: false, + }, + { + name: "Set Vast Unwrapper to true in request context for multiple bids with different type", + 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", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }}, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + url: UnwrapURL, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + expectedBids: []*adapters.TypedBid{{ + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }, + }, + setup: func() { + mockMetricsEngine.EXPECT().RecordRequestStatus("pubmatic", "0").AnyTimes() + mockMetricsEngine.EXPECT().RecordWrapperCount("pubmatic", "0").AnyTimes() + mockMetricsEngine.EXPECT().RecordRequestTime("pubmatic", gomock.Any()).AnyTimes() + }, + unwrapRequest: func(w http.ResponseWriter, req *http.Request) { + w.Header().Add("unwrap-status", "0") + w.Header().Add("unwrap-count", "0") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(inlineXMLAdM)) + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + m := VastUnwrapModule{ + Cfg: tt.args.module.Cfg, + Enabled: true, + MetricsEngine: mockMetricsEngine, + unwrapRequest: tt.unwrapRequest, + } + _, err := m.handleRawBidderResponseHook(tt.args.moduleInvocationCtx, tt.args.payload, "test") + if !assert.NoError(t, err, tt.wantErr) { + return + } + if tt.args.moduleInvocationCtx.ModuleContext != nil && tt.args.wantAdM { + assert.Equal(t, inlineXMLAdM, tt.args.payload.Bids[0].Bid.AdM, "AdM is not updated correctly after executing RawBidderResponse hook.") + } + }) + } +} diff --git a/modules/pubmatic/vastunwrap/models/request.go b/modules/pubmatic/vastunwrap/models/request.go new file mode 100644 index 00000000000..c558fdf0912 --- /dev/null +++ b/modules/pubmatic/vastunwrap/models/request.go @@ -0,0 +1,6 @@ +package models + +type RequestCtx struct { + UA string + VastUnwrapEnabled bool +} diff --git a/modules/pubmatic/vastunwrap/module.go b/modules/pubmatic/vastunwrap/module.go new file mode 100644 index 00000000000..1898e88e78b --- /dev/null +++ b/modules/pubmatic/vastunwrap/module.go @@ -0,0 +1,75 @@ +package vastunwrap + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + vastunwrap "git.pubmatic.com/vastunwrap" + + unWrapCfg "git.pubmatic.com/vastunwrap/config" + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/moduledeps" + metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/vastunwrap/stats" +) + +type VastUnwrapModule struct { + Cfg unWrapCfg.VastUnWrapCfg `mapstructure:"vastunwrap_cfg" json:"vastunwrap_cfg"` + TrafficPercentage int `mapstructure:"traffic_percentage" json:"traffic_percentage"` + Enabled bool `mapstructure:"enabled" json:"enabled"` + MetricsEngine metrics.MetricsEngine + unwrapRequest func(w http.ResponseWriter, r *http.Request) +} + +func Builder(rawCfg json.RawMessage, deps moduledeps.ModuleDeps) (interface{}, error) { + return initVastUnwrap(rawCfg, deps) +} + +func initVastUnwrap(rawCfg json.RawMessage, deps moduledeps.ModuleDeps) (VastUnwrapModule, error) { + t := time.Now() + defer glog.Infof("Time taken by initVastUnwrap---%v", time.Since(t).Milliseconds()) + vastUnwrapModuleCfg := VastUnwrapModule{} + err := json.Unmarshal(rawCfg, &vastUnwrapModuleCfg) + if err != nil { + return vastUnwrapModuleCfg, fmt.Errorf("invalid vastunwrap config: %v", err) + } + vastunwrap.InitUnWrapperConfig(vastUnwrapModuleCfg.Cfg) + metricEngine, err := metrics.NewMetricsEngine(deps) + if err != nil { + return vastUnwrapModuleCfg, fmt.Errorf("Prometheus registry is nil") + } + return VastUnwrapModule{ + Cfg: vastUnwrapModuleCfg.Cfg, + TrafficPercentage: vastUnwrapModuleCfg.TrafficPercentage, + Enabled: vastUnwrapModuleCfg.Enabled, + MetricsEngine: metricEngine, + unwrapRequest: vastunwrap.UnwrapRequest, + }, nil +} + +// HandleRawBidderResponseHook fetches rCtx and check for vast unwrapper flag to enable/disable vast unwrapping feature +func (m VastUnwrapModule) HandleRawBidderResponseHook( + _ context.Context, + miCtx hookstage.ModuleInvocationContext, + payload hookstage.RawBidderResponsePayload, +) (hookstage.HookResult[hookstage.RawBidderResponsePayload], error) { + if m.Enabled { + return m.handleRawBidderResponseHook(miCtx, payload, UnwrapURL) + } + return hookstage.HookResult[hookstage.RawBidderResponsePayload]{}, nil +} + +// HandleEntrypointHook retrieves vast un-wrapper flag and User-agent provided in request context +func (m VastUnwrapModule) HandleEntrypointHook( + ctx context.Context, + miCtx hookstage.ModuleInvocationContext, + payload hookstage.EntrypointPayload, +) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + if m.Enabled { + return handleEntrypointHook(ctx, miCtx, payload, m) + } + return hookstage.HookResult[hookstage.EntrypointPayload]{}, nil +} diff --git a/modules/pubmatic/vastunwrap/module_test.go b/modules/pubmatic/vastunwrap/module_test.go new file mode 100644 index 00000000000..18e16a70038 --- /dev/null +++ b/modules/pubmatic/vastunwrap/module_test.go @@ -0,0 +1,309 @@ +package vastunwrap + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + + unWrapCfg "git.pubmatic.com/vastunwrap/config" + + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + metrics_cfg "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/modules/moduledeps" + "github.com/prebid/prebid-server/v2/modules/pubmatic/vastunwrap/models" + mock_stats "github.com/prebid/prebid-server/v2/modules/pubmatic/vastunwrap/stats/mock" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" +) + +var vastXMLAdM = "PubMatic" +var invalidVastXMLAdM = "PubMatic" +var inlineXMLAdM = "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&er=[ERRORCODE]https://track.dsptracker.com?p=1234&er=[ERRORCODE]https://aktrack.pubmatic.com/AdServer/AdDisplayTrackerServlet?operId=1&pubId=64195&siteId=47105&adId=1405154&adType=13&adServerId=243&kefact=1.000000&kaxefact=1.000000&kadNetFrequecy=0&kadwidth=0&kadheight=0&kadsizeid=97&kltstamp=1536933242&indirectAdId=0&adServerOptimizerId=2&ranreq=0.05969169352174375&kpbmtpfact=11.000000&dcId=1&tldId=0&passback=0&svr=ktk57&ekefact=er2bW2sDAwCra06ACbsIQySn5nqBtYsTl8fy5lupAexh37D_&ekaxefact=er2bW4EDAwB_LQpJJ23Fq0DcNC-NSAFXdpSQC8XBk_S33_Fa&ekpbmtpfact=er2bW5MDAwDJHdBnLBt5IrRuh7x0oqp_tjIALv_VvSQDAl6R&crID=m:1_x:3_y:3_p:11_va:3&lpu=ae.com&ucrid=678722001014421372&campaignId=16774&creativeId=0&pctr=0.000000&wDSPByrId=511&wDspId=27&wbId=0&wrId=0&wAdvID=3170&isRTB=1&rtbId=EBCA079F-8D7C-45B8-B733-92951F670AA1&imprId=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&oid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&pageURL=http%253A%252F%252Fowsdk-stagingams.pubmatic.com%253A8443%252Fvast-validator%252F%2523&sec=1&pmc=1https://DspImpressionTracker.com/https://mytracking.com/linear/closehttps://mytracking.com/linear/skiphttps://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=1https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=2https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=3https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=4https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=5https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=600:00:04https://www.automationtester.inhttps://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=99https://stagingams.pubmatic.com:8443/openwrap/media/pubmatic.mp4https://stagingams.pubmatic.com:8443/openwrap/media/pubmatic.mp4https://stagingams.pubmatic.com:8443/openwrap/media/mp4-sample-3.mp4" + +func TestVastUnwrapModuleHandleEntrypointHook(t *testing.T) { + type fields struct { + cfg VastUnwrapModule + } + type args struct { + ctx context.Context + miCtx hookstage.ModuleInvocationContext + payload hookstage.EntrypointPayload + } + tests := []struct { + name string + fields fields + args args + want hookstage.HookResult[hookstage.EntrypointPayload] + wantErr bool + }{ + { + name: "Vast unwrap is enabled in the config", + fields: fields{cfg: VastUnwrapModule{Enabled: true, Cfg: unWrapCfg.VastUnWrapCfg{ + HTTPConfig: unWrapCfg.HttpConfig{MaxIdleConns: 100, MaxIdleConnsPerHost: 1, IdleConnTimeout: 300}, + APPConfig: unWrapCfg.AppConfig{Host: "", Port: 0, UnwrapDefaultTimeout: 100, Debug: 1}, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", RefershIntervalInSec: 1}, + ServerConfig: unWrapCfg.ServerConfig{ServerName: "", DCName: "OW_DC"}, + }, + TrafficPercentage: 2}}, + args: args{ + miCtx: hookstage.ModuleInvocationContext{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + ctx := context.WithValue(context.Background(), VastUnwrapEnabled, "1") + r, _ := http.NewRequestWithContext(ctx, "", "", nil) + return r + }(), + }}, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + }, + { + name: "Vast unwrap is disabled in the config", + fields: fields{ + cfg: VastUnwrapModule{Enabled: false, Cfg: unWrapCfg.VastUnWrapCfg{ + HTTPConfig: unWrapCfg.HttpConfig{MaxIdleConns: 100, MaxIdleConnsPerHost: 1, IdleConnTimeout: 300}, + APPConfig: unWrapCfg.AppConfig{Host: "", Port: 0, UnwrapDefaultTimeout: 100, Debug: 1}, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", RefershIntervalInSec: 1}, + ServerConfig: unWrapCfg.ServerConfig{ServerName: "", DCName: "OW_DC"}, + }, + TrafficPercentage: 2}}, + args: args{ + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + ctx := context.WithValue(context.Background(), VastUnwrapEnabled, "1") + r, _ := http.NewRequestWithContext(ctx, "", "", nil) + return r + }(), + }}, + want: hookstage.HookResult[hookstage.EntrypointPayload]{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := VastUnwrapModule{ + Cfg: tt.fields.cfg.Cfg, + Enabled: tt.fields.cfg.Enabled, + TrafficPercentage: tt.fields.cfg.TrafficPercentage, + } + got, err := m.HandleEntrypointHook(tt.args.ctx, tt.args.miCtx, tt.args.payload) + if !assert.NoError(t, err, tt.wantErr) { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("VastUnwrapModule.HandleEntrypointHook() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVastUnwrapModuleHandleRawBidderResponseHook(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockMetricsEngine := mock_stats.NewMockMetricsEngine(ctrl) + type fields struct { + cfg VastUnwrapModule + } + type args struct { + in0 context.Context + miCtx hookstage.ModuleInvocationContext + payload hookstage.RawBidderResponsePayload + wantAdM bool + } + tests := []struct { + name string + fields fields + args args + want hookstage.HookResult[hookstage.RawBidderResponsePayload] + wantErr bool + setup func() + unwrapRequest func(w http.ResponseWriter, req *http.Request) + }{ + { + name: "Vast unwrap is enabled in the config", + fields: fields{cfg: VastUnwrapModule{Enabled: true, Cfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + HTTPConfig: unWrapCfg.HttpConfig{MaxIdleConns: 100, MaxIdleConnsPerHost: 1, IdleConnTimeout: 300}, + APPConfig: unWrapCfg.AppConfig{Host: "", Port: 0, UnwrapDefaultTimeout: 1000, Debug: 1}, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", RefershIntervalInSec: 1}, + ServerConfig: unWrapCfg.ServerConfig{ServerName: "", DCName: "OW_DC"}, + }, + TrafficPercentage: 2}}, + args: args{ + miCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + 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", + }, + wantAdM: true, + }, + setup: func() { + mockMetricsEngine.EXPECT().RecordRequestStatus("pubmatic", "0") + mockMetricsEngine.EXPECT().RecordWrapperCount("pubmatic", "1") + mockMetricsEngine.EXPECT().RecordRequestTime("pubmatic", gomock.Any()) + }, + 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)) + }, + + want: hookstage.HookResult[hookstage.RawBidderResponsePayload]{}, + }, + { + name: "Vast unwrap is disabled in the config", + fields: fields{cfg: VastUnwrapModule{Enabled: false, Cfg: unWrapCfg.VastUnWrapCfg{ + HTTPConfig: unWrapCfg.HttpConfig{MaxIdleConns: 100, MaxIdleConnsPerHost: 1, IdleConnTimeout: 300}, + APPConfig: unWrapCfg.AppConfig{Host: "", Port: 0, UnwrapDefaultTimeout: 100, Debug: 1}, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", RefershIntervalInSec: 1}, + ServerConfig: unWrapCfg.ServerConfig{ServerName: "", DCName: "OW_DC"}, + }, + TrafficPercentage: 2}}, + args: args{ + miCtx: hookstage.ModuleInvocationContext{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false}}}, + payload: hookstage.RawBidderResponsePayload{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "
This is an Ad
", + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }}, + }}, + want: hookstage.HookResult[hookstage.RawBidderResponsePayload]{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + m := VastUnwrapModule{ + Cfg: tt.fields.cfg.Cfg, + Enabled: tt.fields.cfg.Enabled, + MetricsEngine: mockMetricsEngine, + unwrapRequest: tt.unwrapRequest, + } + _, err := m.HandleRawBidderResponseHook(tt.args.in0, tt.args.miCtx, tt.args.payload) + if !assert.NoError(t, err, tt.wantErr) { + return + } + if tt.args.wantAdM { + assert.Equal(t, inlineXMLAdM, tt.args.payload.Bids[0].Bid.AdM, "got, tt.want AdM is not updatd correctly after executing RawBidderResponse hook.") + } + }) + } +} + +func TestBuilder(t *testing.T) { + type args struct { + rawCfg json.RawMessage + deps moduledeps.ModuleDeps + } + tests := []struct { + name string + args args + want VastUnwrapModule + wantErr bool + }{ + { + name: "Valid vast unwrap config", + args: args{ + rawCfg: json.RawMessage(`{"enabled":true,"vastunwrap_cfg":{"app_config":{"debug":1,"unwrap_default_timeout":100},"max_wrapper_support":5,"http_config":{"idle_conn_timeout":300,"max_idle_conns":100,"max_idle_conns_per_host":1},"log_config":{"debug_log_file":"/tmp/debug.log","error_log_file":"/tmp/error.log"},"server_config":{"dc_name":"OW_DC"},"stat_config":{"host":"10.172.141.13","port":8080,"referesh_interval_in_sec":1}}}`), + deps: moduledeps.ModuleDeps{ + MetricsRegistry: metrics_cfg.MetricsRegistry{ + metrics_cfg.PrometheusRegistry: prometheus.NewRegistry(), + }, + MetricsCfg: &config.Metrics{ + Prometheus: config.PrometheusMetrics{ + Port: 14404, + Namespace: "ow", + Subsystem: "pbs", + TimeoutMillisRaw: 10, + }, + }, + }, + }, + want: VastUnwrapModule{ + Enabled: true, + Cfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + HTTPConfig: unWrapCfg.HttpConfig{MaxIdleConns: 100, MaxIdleConnsPerHost: 1, IdleConnTimeout: 300}, + APPConfig: unWrapCfg.AppConfig{Host: "", Port: 0, UnwrapDefaultTimeout: 100, Debug: 1}, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", RefershIntervalInSec: 1}, + ServerConfig: unWrapCfg.ServerConfig{ServerName: "", DCName: "OW_DC"}, + }, + }, + wantErr: false, + }, + { + name: "Invalid vast unwrap config", + args: args{ + rawCfg: json.RawMessage(`{"enabled": 1,"vastunwrap_cfg":{"app_config":{"debug":1,"unwrap_default_timeout":100},"max_wrapper_support":5,"http_config":{"idle_conn_timeout":300,"max_idle_conns":100,"max_idle_conns_per_host":1},"log_config":{"debug_log_file":"/tmp/debug.log","error_log_file":"/tmp/error.log"},"server_config":{"dc_name":"OW_DC"},"stat_config":{"host":"10.172.141.13","port":8080,"referesh_interval_in_sec":1}}}`), + deps: moduledeps.ModuleDeps{ + MetricsRegistry: metrics_cfg.MetricsRegistry{ + metrics_cfg.PrometheusRegistry: prometheus.NewRegistry(), + }, + MetricsCfg: &config.Metrics{ + Prometheus: config.PrometheusMetrics{ + Port: 14404, + Namespace: "ow", + Subsystem: "pbs", + TimeoutMillisRaw: 10, + }, + }, + }}, + want: VastUnwrapModule{ + Enabled: true, + Cfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + HTTPConfig: unWrapCfg.HttpConfig{MaxIdleConns: 100, MaxIdleConnsPerHost: 1, IdleConnTimeout: 300}, + APPConfig: unWrapCfg.AppConfig{Host: "", Port: 0, UnwrapDefaultTimeout: 100, Debug: 1}, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", RefershIntervalInSec: 1}, + ServerConfig: unWrapCfg.ServerConfig{ServerName: "", DCName: "OW_DC"}, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Builder(tt.args.rawCfg, tt.args.deps) + if (err != nil) == tt.wantErr { + return + } + got1, _ := got.(VastUnwrapModule) + if !reflect.DeepEqual(got1.Cfg, tt.want.Cfg) { + t.Errorf("initVastUnwrap() = %#v, want %#v", got, tt.want) + } + if !reflect.DeepEqual(got1.Enabled, tt.want.Enabled) { + t.Errorf("initVastUnwrap() = %#v, want %#v", got, tt.want) + } + }) + } +} diff --git a/modules/pubmatic/vastunwrap/respwriter.go b/modules/pubmatic/vastunwrap/respwriter.go new file mode 100644 index 00000000000..1898fe73920 --- /dev/null +++ b/modules/pubmatic/vastunwrap/respwriter.go @@ -0,0 +1,107 @@ +package vastunwrap + +import ( + "bytes" + "fmt" + "net/http" +) + +type CustomRecorder struct { + HeaderMap http.Header + Body *bytes.Buffer + Code int + wroteHeader bool + snapHeader http.Header // snapshot of HeaderMap at first Write +} + +// NewCustomRecorder returns an initialized ResponseRecorder. +func NewCustomRecorder() *CustomRecorder { + return &CustomRecorder{ + HeaderMap: make(http.Header), + Body: new(bytes.Buffer), + Code: 200, + } +} + +// Header implements http.ResponseWriter. It returns the response +// headers to mutate within a handler. To test the headers that were +// written after a handler completes, use the Result method and see +// the returned Response value's Header. +func (r *CustomRecorder) Header() http.Header { + m := r.HeaderMap + if m == nil { + m = make(http.Header) + r.HeaderMap = m + } + return m +} + +// Write implements http.ResponseWriter. The data is written to +// r.Body, if not nil. +func (r *CustomRecorder) Write(data []byte) (int, error) { + r.writeHeader(data, "") + if r.Body != nil { + r.Body.Write(data) + } + return len(data), nil +} + +func checkWriteHeaderCode(code int) { + // Issue 22880: require valid WriteHeader status codes. + // For now we only enforce that it's three digits. + // In the future we might block things over 599 (600 and above aren't defined + // at https://httpwg.org/specs/rfc7231.html#status.codes) + // and we might block under 200 (once we have more mature 1xx support). + // But for now any three digits. + // + // We used to send "HTTP/1.1 000 0" on the wire in responses but there's + // no equivalent bogus thing we can realistically send in HTTP/2, + // so we'll consistently panic instead and help people find their bugs + // early. (We can't return an error from WriteHeader even if we wanted to.) + if code < 100 || code > 999 { + panic(fmt.Sprintf("invalid WriteHeader code %v", code)) + } +} + +// writeHeader writes a header if it was not written yet and +// detects Content-Type if needed. +// +// bytes or str are the beginning of the response body. +// We pass both to avoid unnecessarily generate garbage +// in r.WriteString which was created for performance reasons. +// Non-nil bytes win. +func (r *CustomRecorder) writeHeader(b []byte, str string) { + if r.wroteHeader { + return + } + if len(str) > 512 { + str = str[:512] + } + + m := r.Header() + + _, hasType := m["Content-Type"] + hasTE := m.Get("Transfer-Encoding") != "" + if !hasType && !hasTE { + if b == nil { + b = []byte(str) + } + m.Set("Content-Type", http.DetectContentType(b)) + } + + r.WriteHeader(200) +} + +// WriteHeader implements http.ResponseWriter. +func (r *CustomRecorder) WriteHeader(code int) { + if r.wroteHeader { + return + } + checkWriteHeaderCode(code) + r.Code = code + r.wroteHeader = true + if r.HeaderMap == nil { + r.HeaderMap = make(http.Header) + } + r.snapHeader = r.HeaderMap.Clone() +} diff --git a/modules/pubmatic/vastunwrap/stats/metrics.go b/modules/pubmatic/vastunwrap/stats/metrics.go new file mode 100644 index 00000000000..2f85069e3dc --- /dev/null +++ b/modules/pubmatic/vastunwrap/stats/metrics.go @@ -0,0 +1,109 @@ +package metrics + +import ( + "errors" + "time" + + "github.com/prebid/prebid-server/v2/config" + metrics_cfg "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/modules/moduledeps" + + "github.com/prometheus/client_golang/prometheus" +) + +const ( + bidderLabel = "bidder" + pubIdLabel = "pub_id" + statusLabel = "status" + wrapperCountLabel = "wrapper_count" +) + +// MetricsEngine is a generic interface to record metrics into the desired backend +type MetricsEngine interface { + RecordRequestStatus(bidder, status string) + RecordWrapperCount(bidder string, wrapper_count string) + RecordRequestTime(bidder string, readTime time.Duration) +} + +// Metrics defines the datatype which will implement MetricsEngine +type Metrics struct { + Registry *prometheus.Registry + requests *prometheus.CounterVec + wrapperCount *prometheus.CounterVec + requestTime *prometheus.HistogramVec +} + +// NewMetricsEngine reads the configuration and returns the appropriate metrics engine +// for this instance. +func NewMetricsEngine(cfg moduledeps.ModuleDeps) (*Metrics, error) { + metrics := Metrics{} + // Set up the Prometheus metrics engine. + if cfg.MetricsCfg != nil && cfg.MetricsRegistry != nil && cfg.MetricsRegistry[metrics_cfg.PrometheusRegistry] != nil { + prometheusRegistry, _ := cfg.MetricsRegistry[metrics_cfg.PrometheusRegistry].(*prometheus.Registry) + metrics.Registry = prometheusRegistry + } + if metrics.Registry == nil { + return &metrics, errors.New("Prometheus registry is nil") + } + metrics.requests = newCounter(cfg.MetricsCfg.Prometheus, metrics.Registry, + "vastunwrap_status", + "Count of vast unwrap requests labeled by status", + []string{bidderLabel, statusLabel}) + metrics.wrapperCount = newCounter(cfg.MetricsCfg.Prometheus, metrics.Registry, + "vastunwrap_wrapper_count", + "Count of vast unwrap levels labeled by bidder", + []string{bidderLabel, wrapperCountLabel}) + metrics.requestTime = newHistogramVec(cfg.MetricsCfg.Prometheus, metrics.Registry, + "vastunwrap_request_time", + "Time taken to serve the vast unwrap request in Milliseconds", []string{bidderLabel}, + []float64{50, 100, 200, 300, 500}) + return &metrics, nil +} + +func newCounter(cfg config.PrometheusMetrics, registry *prometheus.Registry, name, help string, labels []string) *prometheus.CounterVec { + opts := prometheus.CounterOpts{ + Namespace: cfg.Namespace, + Subsystem: cfg.Subsystem, + Name: name, + Help: help, + } + counter := prometheus.NewCounterVec(opts, labels) + registry.MustRegister(counter) + return counter +} + +func newHistogramVec(cfg config.PrometheusMetrics, registry *prometheus.Registry, name, help string, labels []string, buckets []float64) *prometheus.HistogramVec { + opts := prometheus.HistogramOpts{ + Namespace: cfg.Namespace, + Subsystem: cfg.Subsystem, + Name: name, + Help: help, + Buckets: buckets, + } + histogram := prometheus.NewHistogramVec(opts, labels) + registry.MustRegister(histogram) + return histogram +} + +// RecordRequest record counter with vast unwrap status +func (m *Metrics) RecordRequestStatus(bidder, status string) { + m.requests.With(prometheus.Labels{ + bidderLabel: bidder, + statusLabel: status, + }).Inc() +} + +// RecordWrapperCount record counter of wrapper levels +func (m *Metrics) RecordWrapperCount(bidder, wrapper_count string) { + m.wrapperCount.With(prometheus.Labels{ + bidderLabel: bidder, + wrapperCountLabel: wrapper_count, + }).Inc() +} + +// RecordRequestReadTime records time takent to complete vast unwrap +func (m *Metrics) RecordRequestTime(bidder string, requestTime time.Duration) { + m.requestTime.With(prometheus.Labels{ + bidderLabel: bidder, + }).Observe(float64(requestTime.Milliseconds())) +} diff --git a/modules/pubmatic/vastunwrap/stats/metrics_test.go b/modules/pubmatic/vastunwrap/stats/metrics_test.go new file mode 100644 index 00000000000..a3856146c5d --- /dev/null +++ b/modules/pubmatic/vastunwrap/stats/metrics_test.go @@ -0,0 +1,107 @@ +package metrics + +import ( + "testing" + "time" + + "github.com/prebid/prebid-server/v2/config" + metrics_cfg "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/modules/moduledeps" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/assert" +) + +func createMetricsForTesting() *Metrics { + cfg := moduledeps.ModuleDeps{ + MetricsRegistry: metrics_cfg.MetricsRegistry{ + metrics_cfg.PrometheusRegistry: prometheus.NewRegistry(), + }, + MetricsCfg: &config.Metrics{ + Prometheus: config.PrometheusMetrics{ + Port: 14404, + Namespace: "ow", + Subsystem: "pbs", + TimeoutMillisRaw: 10, + }, + }, + } + metrics_engine, err := NewMetricsEngine(cfg) + if err != nil { + return &Metrics{} + } + return metrics_engine +} + +func TestRecordRequestTime(t *testing.T) { + m := createMetricsForTesting() + + m.RecordRequestTime("pubmatic", time.Millisecond*250) + + result := getHistogramFromHistogramVec(m.requestTime, "bidder", "pubmatic") + assertHistogram(t, result, 1, 250) +} +func TestRecordRequestStatus(t *testing.T) { + m := createMetricsForTesting() + + m.RecordRequestStatus("pubmatic", "0") + + assertCounterVecValue(t, "Record_Request_Status", "Record_Request_Status_Success", m.requests, float64(1), prometheus.Labels{ + "bidder": "pubmatic", + "status": "0", + }) +} + +func TestRecordWrapperCount(t *testing.T) { + m := createMetricsForTesting() + + m.RecordWrapperCount("pubmatic", "1") + + assertCounterVecValue(t, "Record_Wrapper_Count", "Record_Wrapper_Count", m.wrapperCount, float64(1), prometheus.Labels{ + "bidder": "pubmatic", + "wrapper_count": "1", + }) +} + +func assertCounterValue(t *testing.T, description, name string, counter prometheus.Counter, expected float64) { + m := dto.Metric{} + counter.Write(&m) + actual := *m.GetCounter().Value + + assert.Equal(t, expected, actual, description) +} + +func assertCounterVecValue(t *testing.T, description, name string, counterVec *prometheus.CounterVec, expected float64, labels prometheus.Labels) { + counter := counterVec.With(labels) + assertCounterValue(t, description, name, counter, expected) +} + +func assertHistogram(t *testing.T, histogram dto.Histogram, expectedCount uint64, expectedSum float64) { + assert.Equal(t, expectedCount, histogram.GetSampleCount()) + assert.Equal(t, expectedSum, histogram.GetSampleSum()) +} +func getHistogramFromHistogramVec(histogram *prometheus.HistogramVec, labelKey, labelValue string) dto.Histogram { + var result dto.Histogram + processMetrics(histogram, func(m dto.Metric) { + for _, label := range m.GetLabel() { + if label.GetName() == labelKey && label.GetValue() == labelValue { + result = *m.GetHistogram() + } + } + }) + return result +} + +func processMetrics(collector prometheus.Collector, handler func(m dto.Metric)) { + collectorChan := make(chan prometheus.Metric) + go func() { + collector.Collect(collectorChan) + close(collectorChan) + }() + + for metric := range collectorChan { + dtoMetric := dto.Metric{} + metric.Write(&dtoMetric) + handler(dtoMetric) + } +} diff --git a/modules/pubmatic/vastunwrap/stats/mock/mock.go b/modules/pubmatic/vastunwrap/stats/mock/mock.go new file mode 100644 index 00000000000..b0c899010eb --- /dev/null +++ b/modules/pubmatic/vastunwrap/stats/mock/mock.go @@ -0,0 +1,70 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/PubMatic-OpenWrap/prebid-server/modules/pubmatic/vastunwrap/stats (interfaces: MetricsEngine) + +// Package mock_stats is a generated GoMock package. +package mock + +import ( + gomock "github.com/golang/mock/gomock" + reflect "reflect" + time "time" +) + +// MockMetricsEngine is a mock of MetricsEngine interface +type MockMetricsEngine struct { + ctrl *gomock.Controller + recorder *MockMetricsEngineMockRecorder +} + +// MockMetricsEngineMockRecorder is the mock recorder for MockMetricsEngine +type MockMetricsEngineMockRecorder struct { + mock *MockMetricsEngine +} + +// NewMockMetricsEngine creates a new mock instance +func NewMockMetricsEngine(ctrl *gomock.Controller) *MockMetricsEngine { + mock := &MockMetricsEngine{ctrl: ctrl} + mock.recorder = &MockMetricsEngineMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockMetricsEngine) EXPECT() *MockMetricsEngineMockRecorder { + return m.recorder +} + +// RecordRequestStatus mocks base method +func (m *MockMetricsEngine) RecordRequestStatus(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordRequestStatus", arg0, arg1) +} + +// RecordRequestStatus indicates an expected call of RecordRequestStatus +func (mr *MockMetricsEngineMockRecorder) RecordRequestStatus(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordRequestStatus", reflect.TypeOf((*MockMetricsEngine)(nil).RecordRequestStatus), arg0, arg1) +} + +// RecordWrapperCount mocks base method +func (m *MockMetricsEngine) RecordWrapperCount(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordWrapperCount", arg0, arg1) +} + +// RecordWrapperCount indicates an expected call of RecordRequestStatus +func (mr *MockMetricsEngineMockRecorder) RecordWrapperCount(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordWrapperCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordWrapperCount), arg0, arg1) +} + +// RecordRequestTime mocks base method +func (m *MockMetricsEngine) RecordRequestTime(arg0 string, arg1 time.Duration) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordRequestTime", arg0, arg1) +} + +// RecordRequestTime indicates an expected call of RecordRequestTime +func (mr *MockMetricsEngineMockRecorder) RecordRequestTime(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordRequestTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordRequestTime), arg0, arg1) +} diff --git a/modules/pubmatic/vastunwrap/unwrap_service.go b/modules/pubmatic/vastunwrap/unwrap_service.go new file mode 100644 index 00000000000..915c403d5d4 --- /dev/null +++ b/modules/pubmatic/vastunwrap/unwrap_service.go @@ -0,0 +1,52 @@ +package vastunwrap + +import ( + "net/http" + "runtime/debug" + "strconv" + "strings" + "time" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/adapters" +) + +func (m VastUnwrapModule) doUnwrapandUpdateBid(bid *adapters.TypedBid, userAgent string, unwrapURL string, accountID string, bidder string) { + startTime := time.Now() + var wrapperCnt int64 + var respStatus string + if bid == nil || bid.Bid == nil || bid.Bid.AdM == "" { + return + } + defer func() { + if r := recover(); r != nil { + glog.Errorf("AdM:[%s] Error:[%v] stacktrace:[%s]", bid.Bid.AdM, r, string(debug.Stack())) + } + respTime := time.Since(startTime) + m.MetricsEngine.RecordRequestTime(bidder, respTime) + m.MetricsEngine.RecordRequestStatus(bidder, respStatus) + if respStatus == "0" { + m.MetricsEngine.RecordWrapperCount(bidder, strconv.Itoa(int(wrapperCnt))) + } + }() + headers := http.Header{} + headers.Add(ContentType, "application/xml; charset=utf-8") + headers.Add(UserAgent, userAgent) + headers.Add(UnwrapTimeout, strconv.Itoa(m.Cfg.APPConfig.UnwrapDefaultTimeout)) + httpReq, err := http.NewRequest(http.MethodPost, unwrapURL, strings.NewReader(bid.Bid.AdM)) + if err != nil { + return + } + httpReq.Header = headers + httpResp := NewCustomRecorder() + m.unwrapRequest(httpResp, httpReq) + respStatus = httpResp.Header().Get(UnwrapStatus) + wrapperCnt, _ = strconv.ParseInt(httpResp.Header().Get(UnwrapCount), 10, 0) + respBody := httpResp.Body.Bytes() + if httpResp.Code == http.StatusOK { + bid.Bid.AdM = string(respBody) + return + } + glog.Infof("\n UnWrap Response code = %d for BidId = %s ", httpResp.Code, bid.Bid.ID) + return +} diff --git a/modules/pubmatic/vastunwrap/unwrap_service_test.go b/modules/pubmatic/vastunwrap/unwrap_service_test.go new file mode 100644 index 00000000000..ab029a9cdc3 --- /dev/null +++ b/modules/pubmatic/vastunwrap/unwrap_service_test.go @@ -0,0 +1,176 @@ +package vastunwrap + +import ( + "fmt" + "net/http" + "testing" + + "git.pubmatic.com/vastunwrap/config" + unWrapCfg "git.pubmatic.com/vastunwrap/config" + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + mock_stats "github.com/prebid/prebid-server/v2/modules/pubmatic/vastunwrap/stats/mock" + + "github.com/stretchr/testify/assert" +) + +func TestDoUnwrap(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockMetricsEngine := mock_stats.NewMockMetricsEngine(ctrl) + type args struct { + module VastUnwrapModule + bid *adapters.TypedBid + userAgent string + unwrapDefaultTimeout int + url string + wantAdM bool + } + tests := []struct { + name string + args args + setup func() + unwrapRequest func(w http.ResponseWriter, r *http.Request) + }{ + { + name: "doUnwrap for adtype video with Empty Bid", + args: args{ + module: VastUnwrapModule{Cfg: config.VastUnWrapCfg{MaxWrapperSupport: 5, StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", RefershIntervalInSec: 1}, APPConfig: config.AppConfig{UnwrapDefaultTimeout: 1000}}, MetricsEngine: mockMetricsEngine}, + bid: &adapters.TypedBid{ + Bid: &openrtb2.Bid{}, + }, + userAgent: "testUA", + url: UnwrapURL, + }, + }, + { + name: "doUnwrap for adtype video with Empty ADM", + args: args{ + module: VastUnwrapModule{Cfg: config.VastUnWrapCfg{MaxWrapperSupport: 5, StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", RefershIntervalInSec: 1}, APPConfig: config.AppConfig{UnwrapDefaultTimeout: 1000}}, MetricsEngine: mockMetricsEngine}, + bid: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + userAgent: "testUA", + url: UnwrapURL, + }, + }, + { + name: "doUnwrap for adtype video with invalid URL and timeout", + args: args{ + module: VastUnwrapModule{Cfg: config.VastUnWrapCfg{MaxWrapperSupport: 5, StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", RefershIntervalInSec: 1}, APPConfig: config.AppConfig{UnwrapDefaultTimeout: 2}}, MetricsEngine: mockMetricsEngine}, + bid: &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", + }, + userAgent: "testUA", + url: "testURL", + }, + setup: func() { + mockMetricsEngine.EXPECT().RecordRequestStatus("pubmatic", "2") + mockMetricsEngine.EXPECT().RecordRequestTime("pubmatic", gomock.Any()) + }, + unwrapRequest: func(w http.ResponseWriter, req *http.Request) { + w.Header().Add("unwrap-status", "2") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(vastXMLAdM)) + }, + }, + { + name: "doUnwrap for adtype video", + args: args{ + module: 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}, + bid: &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", + }, + userAgent: "testUA", + url: UnwrapURL, + wantAdM: true, + }, + setup: func() { + mockMetricsEngine.EXPECT().RecordRequestStatus("pubmatic", "0") + mockMetricsEngine.EXPECT().RecordWrapperCount("pubmatic", "1") + mockMetricsEngine.EXPECT().RecordRequestTime("pubmatic", gomock.Any()) + }, + 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)) + }, + }, + { + name: "doUnwrap for adtype video with invalid vast xml", + args: args{ + module: VastUnwrapModule{Cfg: config.VastUnWrapCfg{MaxWrapperSupport: 5, StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", RefershIntervalInSec: 1}, APPConfig: config.AppConfig{UnwrapDefaultTimeout: 1000}}, MetricsEngine: mockMetricsEngine}, + bid: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + userAgent: "testUA", + url: UnwrapURL, + wantAdM: false, + }, + setup: func() { + mockMetricsEngine.EXPECT().RecordRequestStatus("pubmatic", "1") + mockMetricsEngine.EXPECT().RecordRequestTime("pubmatic", gomock.Any()) + }, + unwrapRequest: func(w http.ResponseWriter, req *http.Request) { + w.Header().Add("unwrap-status", "1") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(invalidVastXMLAdM)) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + m := VastUnwrapModule{ + Cfg: tt.args.module.Cfg, + Enabled: true, + MetricsEngine: mockMetricsEngine, + unwrapRequest: tt.unwrapRequest, + } + m.doUnwrapandUpdateBid(tt.args.bid, tt.args.userAgent, tt.args.url, "5890", "pubmatic") + if tt.args.bid.Bid.AdM != "" && tt.args.wantAdM { + assert.Equal(t, inlineXMLAdM, tt.args.bid.Bid.AdM, "AdM is not updated correctly after executing RawBidderResponse hook.") + } + }) + } +} diff --git a/openrtb_ext/adpod.go b/openrtb_ext/adpod.go new file mode 100644 index 00000000000..ed3fef3ba09 --- /dev/null +++ b/openrtb_ext/adpod.go @@ -0,0 +1,326 @@ +package openrtb_ext + +import ( + "encoding/json" + "errors" + "strings" +) + +const ( + //BidderOWPrebidCTV for prebid adpod response + BidderOWPrebidCTV BidderName = "prebid_ctv" +) + +var ( + errInvalidAdPodMinDuration = errors.New("imp.video.minduration must be number positive number") + errInvalidAdPodMaxDuration = errors.New("imp.video.maxduration must be number positive non zero number") + errInvalidAdPodDuration = errors.New("imp.video.minduration must be less than imp.video.maxduration") + errInvalidCrossPodAdvertiserExclusionPercent = errors.New("request.ext.adpod.crosspodexcladv must be a number between 0 and 100") + errInvalidCrossPodIABCategoryExclusionPercent = errors.New("request.ext.adpod.crosspodexcliabcat must be a number between 0 and 100") + errInvalidIABCategoryExclusionWindow = errors.New("request.ext.adpod.excliabcatwindow must be postive number") + errInvalidAdvertiserExclusionWindow = errors.New("request.ext.adpod.excladvwindow must be postive number") + errInvalidVideoAdDurationMatching = errors.New("request.ext.adpod.videoaddurationmatching must be exact|roundup") + errInvalidAdPodOffset = errors.New("request.imp.video.ext.offset must be postive number") + errInvalidMinAds = errors.New("%key%.ext.adpod.minads must be positive number") + errInvalidMaxAds = errors.New("%key%.ext.adpod.maxads must be positive number") + errInvalidMinDuration = errors.New("%key%.ext.adpod.adminduration must be positive number") + errInvalidMaxDuration = errors.New("%key%.ext.adpod.admaxduration must be positive number") + errInvalidAdvertiserExclusionPercent = errors.New("%key%.ext.adpod.excladv must be number between 0 and 100") + errInvalidIABCategoryExclusionPercent = errors.New("%key%.ext.adpod.excliabcat must be number between 0 and 100") + errInvalidMinMaxAds = errors.New("%key%.ext.adpod.minads must be less than %key%.ext.adpod.maxads") + errInvalidMinMaxDuration = errors.New("%key%.ext.adpod.adminduration must be less than %key%.ext.adpod.admaxduration") + errInvalidMinMaxDurationRange = errors.New("adpod duration checks for adminduration,admaxduration,minads,maxads are not in video minduration and maxduration duration range") +) + +type OWVideoAdDurationMatchingPolicy = string + +const ( + OWExactVideoAdDurationMatching OWVideoAdDurationMatchingPolicy = `exact` + OWRoundupVideoAdDurationMatching OWVideoAdDurationMatchingPolicy = `roundup` +) + +// ExtCTVBid defines the contract for bidresponse.seatbid.bid[i].ext +type ExtOWBid struct { + ExtBid + AdPod *BidAdPodExt `json:"adpod,omitempty"` + SKAdNetwork json.RawMessage `json:"skadn,omitempty"` +} + +// BidAdPodExt defines the prebid adpod response in bidresponse.ext.adpod parameter +type BidAdPodExt struct { + ReasonCode *int `json:"aprc,omitempty"` + RefBids []string `json:"refbids,omitempty"` //change refbids to bids name +} + +// ExtOWRequest defines the contract for bidrequest.ext +type ExtOWRequest struct { + ExtRequest + AdPod *ExtRequestAdPod `json:"adpod,omitempty"` +} + +// ExtVideoAdPod structure to accept video specific more parameters like adpod +type ExtVideoAdPod struct { + Offset *int `json:"offset,omitempty"` // Minutes from start where this ad is intended to show + AdPod *VideoAdPod `json:"adpod,omitempty"` +} + +// ExtRequestAdPod holds AdPod specific extension parameters at request level +type ExtRequestAdPod struct { + VideoAdPod + CrossPodAdvertiserExclusionPercent *int `json:"crosspodexcladv,omitempty"` //Percent Value - Across multiple impression there will be no ads from same advertiser. Note: These cross pod rule % values can not be more restrictive than per pod + CrossPodIABCategoryExclusionPercent *int `json:"crosspodexcliabcat,omitempty"` //Percent Value - Across multiple impression there will be no ads from same advertiser + IABCategoryExclusionWindow *int `json:"excliabcatwindow,omitempty"` //Duration in minute between pods where exclusive IAB rule needs to be applied + AdvertiserExclusionWindow *int `json:"excladvwindow,omitempty"` //Duration in minute between pods where exclusive advertiser rule needs to be applied + VideoAdDuration []int `json:"videoadduration,omitempty"` //Range of ad durations allowed in the response + VideoAdDurationMatching OWVideoAdDurationMatchingPolicy `json:"videoaddurationmatching,omitempty"` //Flag indicating exact ad duration requirement. (default)empty/exact/round. +} + +// VideoAdPod holds Video AdPod specific extension parameters at impression level +type VideoAdPod struct { + MinAds *int `json:"minads,omitempty"` //Default 1 if not specified + MaxAds *int `json:"maxads,omitempty"` //Default 1 if not specified + MinDuration *int `json:"adminduration,omitempty"` // (adpod.adminduration * adpod.minads) should be greater than or equal to video.minduration + MaxDuration *int `json:"admaxduration,omitempty"` // (adpod.admaxduration * adpod.maxads) should be less than or equal to video.maxduration + video.maxextended + AdvertiserExclusionPercent *int `json:"excladv,omitempty"` // Percent value 0 means none of the ads can be from same advertiser 100 means can have all same advertisers + IABCategoryExclusionPercent *int `json:"excliabcat,omitempty"` // Percent value 0 means all ads should be of different IAB categories. +} + +/* +//UnmarshalJSON will unmarshal extension into ExtVideoAdPod object +func (ext *ExtVideoAdPod) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, ext) +} + +//UnmarshalJSON will unmarshal extension into ExtRequestAdPod object +func (ext *ExtRequestAdPod) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, ext) +} +*/ +//getRequestAdPodError will return request level error message +func getRequestAdPodError(err error) error { + return errors.New(strings.Replace(err.Error(), "%key%", "req.ext", -1)) +} + +// getVideoAdPodError will return video adpod level error message +func getVideoAdPodError(err error) error { + return errors.New(strings.Replace(err.Error(), "%key%", "imp.video.ext", -1)) +} + +func getIntPtr(v int) *int { + return &v +} + +// Validate will validate AdPod object +func (pod *VideoAdPod) Validate() (err []error) { + if nil != pod.MinAds && *pod.MinAds <= 0 { + err = append(err, errInvalidMinAds) + } + + if nil != pod.MaxAds && *pod.MaxAds <= 0 { + err = append(err, errInvalidMaxAds) + } + + if nil != pod.MinDuration && *pod.MinDuration <= 0 { + err = append(err, errInvalidMinDuration) + } + + if nil != pod.MaxDuration && *pod.MaxDuration <= 0 { + err = append(err, errInvalidMaxDuration) + } + + if nil != pod.AdvertiserExclusionPercent && (*pod.AdvertiserExclusionPercent < 0 || *pod.AdvertiserExclusionPercent > 100) { + err = append(err, errInvalidAdvertiserExclusionPercent) + } + + if nil != pod.IABCategoryExclusionPercent && (*pod.IABCategoryExclusionPercent < 0 || *pod.IABCategoryExclusionPercent > 100) { + err = append(err, errInvalidIABCategoryExclusionPercent) + } + + if nil != pod.MinAds && nil != pod.MaxAds && *pod.MinAds > *pod.MaxAds { + err = append(err, errInvalidMinMaxAds) + } + + if nil != pod.MinDuration && nil != pod.MaxDuration && *pod.MinDuration > *pod.MaxDuration { + err = append(err, errInvalidMinMaxDuration) + } + + return +} + +// Validate will validate ExtRequestAdPod object +func (ext *ExtRequestAdPod) Validate() (err []error) { + if nil == ext { + return + } + + if nil != ext.CrossPodAdvertiserExclusionPercent && + (*ext.CrossPodAdvertiserExclusionPercent < 0 || *ext.CrossPodAdvertiserExclusionPercent > 100) { + err = append(err, errInvalidCrossPodAdvertiserExclusionPercent) + } + + if nil != ext.CrossPodIABCategoryExclusionPercent && + (*ext.CrossPodIABCategoryExclusionPercent < 0 || *ext.CrossPodIABCategoryExclusionPercent > 100) { + err = append(err, errInvalidCrossPodIABCategoryExclusionPercent) + } + + if nil != ext.IABCategoryExclusionWindow && *ext.IABCategoryExclusionWindow < 0 { + err = append(err, errInvalidIABCategoryExclusionWindow) + } + + if nil != ext.AdvertiserExclusionWindow && *ext.AdvertiserExclusionWindow < 0 { + err = append(err, errInvalidAdvertiserExclusionWindow) + } + + if len(ext.VideoAdDurationMatching) > 0 && !(OWExactVideoAdDurationMatching == ext.VideoAdDurationMatching || OWRoundupVideoAdDurationMatching == ext.VideoAdDurationMatching) { + err = append(err, errInvalidVideoAdDurationMatching) + } + + if errL := ext.VideoAdPod.Validate(); nil != errL { + for _, errr := range errL { + err = append(err, getRequestAdPodError(errr)) + } + } + + return +} + +// Validate will validate video extension object +func (ext *ExtVideoAdPod) Validate() (err []error) { + if nil != ext.Offset && *ext.Offset < 0 { + err = append(err, errInvalidAdPodOffset) + } + + if nil != ext.AdPod { + if errL := ext.AdPod.Validate(); nil != errL { + for _, errr := range errL { + err = append(err, getRequestAdPodError(errr)) + } + } + } + + return +} + +// SetDefaultValue will set default values if not present +func (pod *VideoAdPod) SetDefaultValue() { + //pod.MinAds setting default value + if nil == pod.MinAds { + pod.MinAds = getIntPtr(1) + } + + //pod.MaxAds setting default value + if nil == pod.MaxAds { + pod.MaxAds = getIntPtr(3) + } + + //pod.AdvertiserExclusionPercent setting default value + if nil == pod.AdvertiserExclusionPercent { + pod.AdvertiserExclusionPercent = getIntPtr(100) + } + + //pod.IABCategoryExclusionPercent setting default value + if nil == pod.IABCategoryExclusionPercent { + pod.IABCategoryExclusionPercent = getIntPtr(100) + } +} + +// SetDefaultValue will set default values if not present +func (ext *ExtRequestAdPod) SetDefaultValue() { + //ext.VideoAdPod setting default value + ext.VideoAdPod.SetDefaultValue() + + //ext.CrossPodAdvertiserExclusionPercent setting default value + if nil == ext.CrossPodAdvertiserExclusionPercent { + ext.CrossPodAdvertiserExclusionPercent = getIntPtr(100) + } + + //ext.CrossPodIABCategoryExclusionPercent setting default value + if nil == ext.CrossPodIABCategoryExclusionPercent { + ext.CrossPodIABCategoryExclusionPercent = getIntPtr(100) + } + + //ext.IABCategoryExclusionWindow setting default value + if nil == ext.IABCategoryExclusionWindow { + ext.IABCategoryExclusionWindow = getIntPtr(0) + } + + //ext.AdvertiserExclusionWindow setting default value + if nil == ext.AdvertiserExclusionWindow { + ext.AdvertiserExclusionWindow = getIntPtr(0) + } +} + +// SetDefaultValue will set default values if not present +func (ext *ExtVideoAdPod) SetDefaultValue() { + //ext.Offset setting default values + if nil == ext.Offset { + ext.Offset = getIntPtr(0) + } + + //ext.AdPod setting default values + if nil == ext.AdPod { + ext.AdPod = &VideoAdPod{} + } + ext.AdPod.SetDefaultValue() +} + +// SetDefaultAdDuration will set default pod ad slot durations +func (pod *VideoAdPod) SetDefaultAdDurations(podMinDuration, podMaxDuration int64) { + //pod.MinDuration setting default adminduration + if nil == pod.MinDuration { + duration := int(podMinDuration / 2) + pod.MinDuration = &duration + } + + //pod.MaxDuration setting default admaxduration + if nil == pod.MaxDuration { + duration := int(podMaxDuration / 2) + pod.MaxDuration = &duration + } +} + +// Merge VideoAdPod Values +func (pod *VideoAdPod) Merge(parent *VideoAdPod) { + //pod.MinAds setting default value + if nil == pod.MinAds { + pod.MinAds = parent.MinAds + } + + //pod.MaxAds setting default value + if nil == pod.MaxAds { + pod.MaxAds = parent.MaxAds + } + + //pod.AdvertiserExclusionPercent setting default value + if nil == pod.AdvertiserExclusionPercent { + pod.AdvertiserExclusionPercent = parent.AdvertiserExclusionPercent + } + + //pod.IABCategoryExclusionPercent setting default value + if nil == pod.IABCategoryExclusionPercent { + pod.IABCategoryExclusionPercent = parent.IABCategoryExclusionPercent + } +} + +// ValidateAdPodDurations will validate adpod min,max durations +func (pod *VideoAdPod) ValidateAdPodDurations(minDuration, maxDuration, maxExtended int64) (err []error) { + if minDuration < 0 { + err = append(err, errInvalidAdPodMinDuration) + } + + if maxDuration <= 0 { + err = append(err, errInvalidAdPodMaxDuration) + } + + if minDuration > maxDuration { + err = append(err, errInvalidAdPodDuration) + } + + if pod.MinAds != nil && pod.MinDuration != nil && pod.MaxDuration != nil && pod.MaxAds != nil { + if ((*pod.MinAds * *pod.MinDuration) <= int(maxDuration)) && (int(minDuration) <= (*pod.MaxAds * *pod.MaxDuration)) { + } else { + err = append(err, errInvalidMinMaxDurationRange) + } + } + return +} diff --git a/openrtb_ext/adpod_test.go b/openrtb_ext/adpod_test.go new file mode 100644 index 00000000000..ecd2ebefee9 --- /dev/null +++ b/openrtb_ext/adpod_test.go @@ -0,0 +1,315 @@ +package openrtb_ext + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVideoAdPod_Validate(t *testing.T) { + type fields struct { + MinAds *int + MaxAds *int + MinDuration *int + MaxDuration *int + AdvertiserExclusionPercent *int + IABCategoryExclusionPercent *int + } + tests := []struct { + name string + fields fields + wantErr []error + }{ + { + name: "ErrInvalidMinAds", + fields: fields{ + MinAds: getIntPtr(-1), + }, + wantErr: []error{errInvalidMinAds}, + }, + { + name: "ZeroMinAds", + fields: fields{ + MinAds: getIntPtr(0), + }, + wantErr: []error{errInvalidMinAds}, + }, + { + name: "ErrInvalidMaxAds", + fields: fields{ + MaxAds: getIntPtr(-1), + }, + wantErr: []error{errInvalidMaxAds}, + }, + { + name: "ZeroMaxAds", + fields: fields{ + MaxAds: getIntPtr(0), + }, + wantErr: []error{errInvalidMaxAds}, + }, + { + name: "ErrInvalidMinDuration", + fields: fields{ + MinDuration: getIntPtr(-1), + }, + wantErr: []error{errInvalidMinDuration}, + }, + { + name: "ZeroMinDuration", + fields: fields{ + MinDuration: getIntPtr(0), + }, + wantErr: []error{errInvalidMinDuration}, + }, + { + name: "ErrInvalidMaxDuration", + fields: fields{ + MaxDuration: getIntPtr(-1), + }, + wantErr: []error{errInvalidMaxDuration}, + }, + { + name: "ZeroMaxDuration", + fields: fields{ + MaxDuration: getIntPtr(0), + }, + wantErr: []error{errInvalidMaxDuration}, + }, + { + name: "ErrInvalidAdvertiserExclusionPercent_NegativeValue", + fields: fields{ + AdvertiserExclusionPercent: getIntPtr(-1), + }, + wantErr: []error{errInvalidAdvertiserExclusionPercent}, + }, + { + name: "ErrInvalidAdvertiserExclusionPercent_InvalidRange", + fields: fields{ + AdvertiserExclusionPercent: getIntPtr(-1), + }, + wantErr: []error{errInvalidAdvertiserExclusionPercent}, + }, + { + name: "ErrInvalidIABCategoryExclusionPercent_Negative", + fields: fields{ + IABCategoryExclusionPercent: getIntPtr(-1), + }, + wantErr: []error{errInvalidIABCategoryExclusionPercent}, + }, + { + name: "ErrInvalidIABCategoryExclusionPercent_InvalidRange", + fields: fields{ + IABCategoryExclusionPercent: getIntPtr(101), + }, + wantErr: []error{errInvalidIABCategoryExclusionPercent}, + }, + { + name: "ErrInvalidMinMaxAds", + fields: fields{ + MinAds: getIntPtr(5), + MaxAds: getIntPtr(2), + }, + wantErr: []error{errInvalidMinMaxAds}, + }, + { + name: "ErrInvalidMinMaxDuration", + fields: fields{ + MinDuration: getIntPtr(5), + MaxDuration: getIntPtr(2), + }, + wantErr: []error{errInvalidMinMaxDuration}, + }, + { + name: "Valid", + fields: fields{ + MinAds: getIntPtr(3), + MaxAds: getIntPtr(4), + MinDuration: getIntPtr(20), + MaxDuration: getIntPtr(30), + AdvertiserExclusionPercent: getIntPtr(100), + IABCategoryExclusionPercent: getIntPtr(100), + }, + wantErr: nil, + }, + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pod := &VideoAdPod{ + MinAds: tt.fields.MinAds, + MaxAds: tt.fields.MaxAds, + MinDuration: tt.fields.MinDuration, + MaxDuration: tt.fields.MaxDuration, + AdvertiserExclusionPercent: tt.fields.AdvertiserExclusionPercent, + IABCategoryExclusionPercent: tt.fields.IABCategoryExclusionPercent, + } + + actualErr := pod.Validate() + assert.Equal(t, tt.wantErr, actualErr) + }) + } +} + +func TestExtRequestAdPod_Validate(t *testing.T) { + type fields struct { + VideoAdPod VideoAdPod + CrossPodAdvertiserExclusionPercent *int + CrossPodIABCategoryExclusionPercent *int + IABCategoryExclusionWindow *int + AdvertiserExclusionWindow *int + VideoAdDurationMatching string + } + tests := []struct { + name string + fields fields + wantErr []error + }{ + { + name: "ErrInvalidCrossPodAdvertiserExclusionPercent_Negative", + fields: fields{ + CrossPodAdvertiserExclusionPercent: getIntPtr(-1), + }, + wantErr: []error{errInvalidCrossPodAdvertiserExclusionPercent}, + }, + { + name: "ErrInvalidCrossPodAdvertiserExclusionPercent_InvalidRange", + fields: fields{ + CrossPodAdvertiserExclusionPercent: getIntPtr(101), + }, + wantErr: []error{errInvalidCrossPodAdvertiserExclusionPercent}, + }, + { + name: "ErrInvalidCrossPodIABCategoryExclusionPercent_Negative", + fields: fields{ + CrossPodIABCategoryExclusionPercent: getIntPtr(-1), + }, + wantErr: []error{errInvalidCrossPodIABCategoryExclusionPercent}, + }, + { + name: "ErrInvalidCrossPodIABCategoryExclusionPercent_InvalidRange", + fields: fields{ + CrossPodIABCategoryExclusionPercent: getIntPtr(101), + }, + wantErr: []error{errInvalidCrossPodIABCategoryExclusionPercent}, + }, + { + name: "ErrInvalidIABCategoryExclusionWindow", + fields: fields{ + IABCategoryExclusionWindow: getIntPtr(-1), + }, + wantErr: []error{errInvalidIABCategoryExclusionWindow}, + }, + { + name: "ErrInvalidAdvertiserExclusionWindow", + fields: fields{ + AdvertiserExclusionWindow: getIntPtr(-1), + }, + wantErr: []error{errInvalidAdvertiserExclusionWindow}, + }, + { + name: "ErrInvalidVideoAdDurationMatching", + fields: fields{ + VideoAdDurationMatching: "invalid", + }, + wantErr: []error{errInvalidVideoAdDurationMatching}, + }, + { + name: "InvalidAdPod", + fields: fields{ + VideoAdPod: VideoAdPod{ + MinAds: getIntPtr(-1), + }, + }, + wantErr: []error{getRequestAdPodError(errInvalidMinAds)}, + }, + { + name: "Valid", + fields: fields{ + CrossPodAdvertiserExclusionPercent: getIntPtr(100), + CrossPodIABCategoryExclusionPercent: getIntPtr(0), + IABCategoryExclusionWindow: getIntPtr(10), + AdvertiserExclusionWindow: getIntPtr(10), + VideoAdPod: VideoAdPod{ + MinAds: getIntPtr(3), + MaxAds: getIntPtr(4), + MinDuration: getIntPtr(20), + MaxDuration: getIntPtr(30), + AdvertiserExclusionPercent: getIntPtr(100), + IABCategoryExclusionPercent: getIntPtr(100), + }, + }, + wantErr: nil, + }, + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ext := &ExtRequestAdPod{ + VideoAdPod: tt.fields.VideoAdPod, + CrossPodAdvertiserExclusionPercent: tt.fields.CrossPodAdvertiserExclusionPercent, + CrossPodIABCategoryExclusionPercent: tt.fields.CrossPodIABCategoryExclusionPercent, + IABCategoryExclusionWindow: tt.fields.IABCategoryExclusionWindow, + AdvertiserExclusionWindow: tt.fields.AdvertiserExclusionWindow, + VideoAdDurationMatching: tt.fields.VideoAdDurationMatching, + } + actualErr := ext.Validate() + assert.Equal(t, tt.wantErr, actualErr) + }) + } +} + +func TestExtVideoAdPod_Validate(t *testing.T) { + type fields struct { + Offset *int + AdPod *VideoAdPod + } + tests := []struct { + name string + fields fields + wantErr []error + }{ + { + name: "ErrInvalidAdPodOffset", + fields: fields{ + Offset: getIntPtr(-1), + }, + wantErr: []error{errInvalidAdPodOffset}, + }, + { + name: "InvalidAdPod", + fields: fields{ + AdPod: &VideoAdPod{ + MinAds: getIntPtr(-1), + }, + }, + wantErr: []error{getRequestAdPodError(errInvalidMinAds)}, + }, + { + name: "Valid", + fields: fields{ + Offset: getIntPtr(10), + AdPod: &VideoAdPod{ + MinAds: getIntPtr(3), + MaxAds: getIntPtr(4), + MinDuration: getIntPtr(20), + MaxDuration: getIntPtr(30), + AdvertiserExclusionPercent: getIntPtr(100), + IABCategoryExclusionPercent: getIntPtr(100), + }, + }, + wantErr: nil, + }, + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ext := &ExtVideoAdPod{ + Offset: tt.fields.Offset, + AdPod: tt.fields.AdPod, + } + actualErr := ext.Validate() + assert.Equal(t, tt.wantErr, actualErr) + }) + } +} diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 2e190389212..aec4219e7df 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -7,7 +7,8 @@ import ( // ExtBid defines the contract for bidresponse.seatbid.bid[i].ext type ExtBid struct { - Prebid *ExtBidPrebid `json:"prebid,omitempty"` + Prebid *ExtBidPrebid `json:"prebid,omitempty"` + Bidder json.RawMessage `json:"bidder,omitempty"` } // ExtBidPrebid defines the contract for bidresponse.seatbid.bid[i].ext.prebid @@ -33,7 +34,8 @@ type ExtBidPrebidFloors struct { FloorRule string `json:"floorRule,omitempty"` FloorRuleValue float64 `json:"floorRuleValue,omitempty"` FloorValue float64 `json:"floorValue,omitempty"` - FloorCurrency string `json:"floorCurrency,omitempty"` + // FloorValueUSD float64 `json:"floorvalueusd,omitempty"` + FloorCurrency string `json:"floorCurrency,omitempty"` } // ExtBidPrebidCache defines the contract for bidresponse.seatbid.bid[i].ext.prebid.cache @@ -75,6 +77,7 @@ type ExtBidPrebidMeta struct { type ExtBidPrebidVideo struct { Duration int `json:"duration"` PrimaryCategory string `json:"primary_category"` + VASTTagID string `json:"vasttagid"` } // ExtBidPrebidEvents defines the contract for bidresponse.seatbid.bid[i].ext.prebid.events @@ -189,4 +192,5 @@ const ( OriginalBidCpmKey = "origbidcpm" OriginalBidCurKey = "origbidcur" Passthrough = "passthrough" + OriginalBidCpmUsdKey = "origbidcpmusd" ) diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 1f6eebbf6d8..edcf0d8941f 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -179,6 +179,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderSonobi, BidderSovrn, BidderSspBC, + BidderSpotX, BidderStroeerCore, BidderSuntContent, BidderTaboola, @@ -193,6 +194,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderUndertone, BidderUnicorn, BidderUnruly, + BidderVASTBidder, BidderVideoByte, BidderVideoHeroes, BidderVidoomy, @@ -457,6 +459,8 @@ const ( BidderSonobi BidderName = "sonobi" BidderSovrn BidderName = "sovrn" BidderSspBC BidderName = "sspBC" + BidderSpotX BidderName = "spotx" + BidderStreamkey BidderName = "streamkey" BidderStroeerCore BidderName = "stroeerCore" BidderSuntContent BidderName = "suntContent" BidderTaboola BidderName = "taboola" @@ -471,6 +475,8 @@ const ( BidderUndertone BidderName = "undertone" BidderUnicorn BidderName = "unicorn" BidderUnruly BidderName = "unruly" + BidderValueImpression BidderName = "valueimpression" + BidderVASTBidder BidderName = "vastbidder" BidderVideoByte BidderName = "videobyte" BidderVideoHeroes BidderName = "videoheroes" BidderVidoomy BidderName = "vidoomy" diff --git a/openrtb_ext/device.go b/openrtb_ext/device.go index 0888d06160f..f8be08eef78 100644 --- a/openrtb_ext/device.go +++ b/openrtb_ext/device.go @@ -34,6 +34,14 @@ type ExtDevice struct { // Description: // Prebid extensions for the Device object. Prebid ExtDevicePrebid `json:"prebid"` + + // Attribute: + // ifa_type + // Type: + // string; optional + // Description: + // Contains source who generated ifa value + IFAType string `json:"ifa_type,omitempty"` } // IOSAppTrackingStatus describes the values for iOS app tracking authorization status. diff --git a/openrtb_ext/floors.go b/openrtb_ext/floors.go index 0e773c65899..a429cbd9201 100644 --- a/openrtb_ext/floors.go +++ b/openrtb_ext/floors.go @@ -88,6 +88,7 @@ type PriceFloorData struct { ModelTimestamp int `json:"modeltimestamp,omitempty"` ModelGroups []PriceFloorModelGroup `json:"modelgroups,omitempty"` FloorProvider string `json:"floorprovider,omitempty"` + UseFetchDataRate *int `json:"usefetchdatarate,omitempty"` } type PriceFloorModelGroup struct { diff --git a/openrtb_ext/imp_pubmatic.go b/openrtb_ext/imp_pubmatic.go index 2a45b491d0a..075729b748d 100644 --- a/openrtb_ext/imp_pubmatic.go +++ b/openrtb_ext/imp_pubmatic.go @@ -9,13 +9,15 @@ import "encoding/json" // WrapExt needs to be sent once per bid request type ExtImpPubmatic struct { - PublisherId string `json:"publisherId"` - AdSlot string `json:"adSlot"` - Dctr string `json:"dctr"` - PmZoneID string `json:"pmzoneid"` - WrapExt json.RawMessage `json:"wrapper,omitempty"` - Keywords []*ExtImpPubmaticKeyVal `json:"keywords,omitempty"` - Kadfloor string `json:"kadfloor,omitempty"` + PublisherId string `json:"publisherId"` + AdSlot string `json:"adSlot"` + Dctr string `json:"dctr,omitempty"` + PmZoneID string `json:"pmzoneid,omitempty"` + WrapExt json.RawMessage `json:"wrapper,omitempty"` + Keywords []*ExtImpPubmaticKeyVal `json:"keywords,omitempty"` + Kadfloor string `json:"kadfloor,omitempty"` + BidViewabilityScore map[string]interface{} `json:"bidViewability,omitempty"` + DealTier *DealTier `json:"dealtier,omitempty"` } // ExtImpPubmaticKeyVal defines the contract for bidrequest.imp[i].ext.prebid.bidder.pubmatic.keywords[i] diff --git a/openrtb_ext/imp_spotx.go b/openrtb_ext/imp_spotx.go new file mode 100644 index 00000000000..ee209b78f6c --- /dev/null +++ b/openrtb_ext/imp_spotx.go @@ -0,0 +1,10 @@ +package openrtb_ext + +type ExtImpSpotX struct { + ChannelID string `json:"channel_id"` + AdUnit string `json:"ad_unit"` + Secure bool `json:"secure,omitempty"` + AdVolume float64 `json:"ad_volume,omitempty"` + PriceFloor int `json:"price_floor,omitempty"` + HideSkin bool `json:"hide_skin,omitempty"` +} diff --git a/openrtb_ext/imp_vastbidder.go b/openrtb_ext/imp_vastbidder.go new file mode 100644 index 00000000000..2923c2dd8d7 --- /dev/null +++ b/openrtb_ext/imp_vastbidder.go @@ -0,0 +1,18 @@ +package openrtb_ext + +// ExtImpVASTBidder defines the contract for bidrequest.imp[i].ext.vastbidder +type ExtImpVASTBidder struct { + Tags []*ExtImpVASTBidderTag `json:"tags,omitempty"` + Parser string `json:"parser,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + Cookies map[string]string `json:"cookies,omitempty"` +} + +// ExtImpVASTBidderTag defines the contract for bidrequest.imp[i].ext.pubmatic.tags[i] +type ExtImpVASTBidderTag struct { + TagID string `json:"tagid"` + URL string `json:"url"` + Duration int `json:"dur"` + Price float64 `json:"price"` + Params map[string]interface{} `json:"params,omitempty"` +} diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 1a98e34d13f..ca9d1d1b014 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -90,6 +90,9 @@ type ExtRequestPrebid struct { // - basic: excludes debugmessages and analytic_tags from output // any other value or an empty string disables trace output at all. Trace string `json:"trace,omitempty"` + + Transparency *TransparencyExt `json:"transparency,omitempty"` + KeyVal map[string]interface{} `json:"keyval,omitempty"` } type AdServerTarget struct { @@ -108,6 +111,15 @@ type AdsCert struct { Enabled bool `json:"enabled,omitempty"` } +type TransparencyRule struct { + Include bool `json:"include,omitempty"` + Keys []string `json:"keys,omitempty"` +} + +type TransparencyExt struct { + Content map[string]TransparencyRule `json:"content,omitempty"` +} + type BidderConfig struct { Bidders []string `json:"bidders,omitempty"` Config *Config `json:"config,omitempty"` @@ -206,6 +218,7 @@ type ExtIncludeBrandCategory struct { Publisher string `json:"publisher"` WithCategory bool `json:"withcategory"` TranslateCategories *bool `json:"translatecategories,omitempty"` + SkipDedup bool `json:"skipdedup,omitempty"` } // MediaTypePriceGranularity specify price granularity configuration at the bid type level @@ -218,6 +231,7 @@ type MediaTypePriceGranularity struct { // PriceGranularity defines the allowed values for bidrequest.ext.prebid.targeting.pricegranularity // or bidrequest.ext.prebid.targeting.mediatypepricegranularity.banner|video|native type PriceGranularity struct { + Test bool `json:"test,omitempty"` Precision *int `json:"precision,omitempty"` Ranges []GranularityRange `json:"ranges,omitempty"` } @@ -331,6 +345,23 @@ func NewPriceGranularityFromLegacyID(v string) (PriceGranularity, bool) { }, }, }, true + case "ow-ctv-med": + return PriceGranularity{ + Precision: &precision2, + Ranges: []GranularityRange{{ + Min: 0, + Max: 100, + Increment: 0.5}}, + }, true + case "testpg": + return PriceGranularity{ + Test: true, + Precision: &precision2, + Ranges: []GranularityRange{{ + Min: 0, + Max: 50, + Increment: 50}}, + }, true } return PriceGranularity{}, false diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go index 5adc192d36f..919ce6a3cef 100644 --- a/openrtb_ext/request_wrapper.go +++ b/openrtb_ext/request_wrapper.go @@ -189,7 +189,7 @@ func (rw *RequestWrapper) RebuildRequest() error { return errors.New("Requestwrapper RebuildRequest called on a nil BidRequest") } - if err := rw.rebuildImp(); err != nil { + if err := rw.RebuildImp(); err != nil { return err } if err := rw.rebuildUserExt(); err != nil { @@ -198,7 +198,7 @@ func (rw *RequestWrapper) RebuildRequest() error { if err := rw.rebuildDeviceExt(); err != nil { return err } - if err := rw.rebuildRequestExt(); err != nil { + if err := rw.RebuildRequestExt(); err != nil { return err } if err := rw.rebuildAppExt(); err != nil { @@ -220,7 +220,7 @@ func (rw *RequestWrapper) RebuildRequest() error { return nil } -func (rw *RequestWrapper) rebuildImp() error { +func (rw *RequestWrapper) RebuildImp() error { if !rw.impWrappersAccessed { return nil } @@ -279,7 +279,7 @@ func (rw *RequestWrapper) rebuildDeviceExt() error { return nil } -func (rw *RequestWrapper) rebuildRequestExt() error { +func (rw *RequestWrapper) RebuildRequestExt() error { if rw.requestExt == nil || !rw.requestExt.Dirty() { return nil } diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go index 8e9f36e8484..e91adaecad9 100644 --- a/openrtb_ext/response.go +++ b/openrtb_ext/response.go @@ -5,8 +5,11 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v19/openrtb3" ) +type NonBidStatusCode openrtb3.LossReason + // ExtBidResponse defines the contract for bidresponse.ext type ExtBidResponse struct { Debug *ExtResponseDebug `json:"debug,omitempty"` @@ -23,6 +26,11 @@ type ExtBidResponse struct { Usersync map[BidderName]*ExtResponseSyncData `json:"usersync,omitempty"` // Prebid defines the contract for bidresponse.ext.prebid Prebid *ExtResponsePrebid `json:"prebid,omitempty"` + + OwMatchedImpression json.RawMessage `json:"matchedimpression,omitempty"` + OwSendAllBids int `json:"sendallbids,omitempty"` + OwLogInfo *OwLogInfo `json:"loginfo,omitempty"` + OwLogger string `json:"owlogger,omitempty"` } // ExtResponseDebug defines the contract for bidresponse.ext.debug @@ -48,7 +56,8 @@ type ExtResponsePrebid struct { Fledge *Fledge `json:"fledge,omitempty"` Targeting map[string]string `json:"targeting,omitempty"` // SeatNonBid holds the array of Bids which are either rejected, no bids inside bidresponse.ext.prebid.seatnonbid - SeatNonBid []SeatNonBid `json:"seatnonbid,omitempty"` + SeatNonBid []SeatNonBid `json:"seatnonbid,omitempty"` + Floors *PriceFloorRules `json:"floors,omitempty"` } // FledgeResponse defines the contract for bidresponse.ext.fledge @@ -83,6 +92,7 @@ type ExtHttpCall struct { RequestHeaders map[string][]string `json:"requestheaders"` ResponseBody string `json:"responsebody"` Status int `json:"status"` + Params map[string]int `json:"params,omitempty"` } // CookieStatus describes the allowed values for bidresponse.ext.usersync.{bidder}.status @@ -118,6 +128,18 @@ type NonBidObject struct { OriginalBidCPM float64 `json:"origbidcpm,omitempty"` OriginalBidCur string `json:"origbidcur,omitempty"` + + //OW specific fields + ID string `json:"id"` + DealPriority int `json:"dealpriority,omitempty"` + DealTierSatisfied bool `json:"dealtiersatisfied,omitempty"` + Meta *ExtBidPrebidMeta `json:"meta,omitempty"` + Targeting map[string]string `json:"targeting,omitempty"` + Type BidType `json:"type,omitempty"` + Video *ExtBidPrebidVideo `json:"video,omitempty"` + BidId string `json:"bidid,omitempty"` + Floors *ExtBidPrebidFloors `json:"floors,omitempty"` + OriginalBidCPMUSD float64 `json:"origbidcpmusd,omitempty"` } // ExtResponseNonBidPrebid represents bidresponse.ext.prebid.seatnonbid[].nonbid[].ext @@ -126,7 +148,9 @@ type ExtResponseNonBidPrebid struct { } type NonBidExt struct { - Prebid ExtResponseNonBidPrebid `json:"prebid"` + Prebid ExtResponseNonBidPrebid `json:"prebid"` + IsAdPod *bool `json:"-"` // OW specific Flag to determine if it is Ad-Pod specific nonbid + } // NonBid represnts the Non Bid Reason (statusCode) for given impression ID diff --git a/openrtb_ext/response_ow.go b/openrtb_ext/response_ow.go new file mode 100644 index 00000000000..523c4a51ce3 --- /dev/null +++ b/openrtb_ext/response_ow.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// OwLogInfo contains the logger, tracker calls to be sent in response +type OwLogInfo struct { + Logger string `json:"logger,omitempty"` + Tracker string `json:"tracker,omitempty"` +} diff --git a/openrtb_ext/supplyChain.go b/openrtb_ext/supplyChain.go index 0ccbd0957fa..c3737c4d5a5 100644 --- a/openrtb_ext/supplyChain.go +++ b/openrtb_ext/supplyChain.go @@ -1,6 +1,10 @@ package openrtb_ext import ( + "fmt" + "net/url" + "strings" + "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/v2/util/ptrutil" ) @@ -19,3 +23,56 @@ func cloneSupplyChain(schain *openrtb2.SupplyChain) *openrtb2.SupplyChain { return &clone } + +// SerializeSupplyChain convert schain object to serialized string +func SerializeSupplyChain(schain *openrtb2.SupplyChain) string { + + if len(schain.Nodes) < 1 { + return "" + } + var serializedSchain strings.Builder + serializedSchain.Grow(256) + + serializedSchain.WriteString(schain.Ver) + serializedSchain.WriteByte(',') + fmt.Fprintf(&serializedSchain, "%d", schain.Complete) + + for _, node := range schain.Nodes { + serializedSchain.WriteByte('!') + + if node.ASI != "" { + serializedSchain.WriteString(url.QueryEscape(node.ASI)) + } + serializedSchain.WriteByte(',') + + if node.SID != "" { + serializedSchain.WriteString(url.QueryEscape(node.SID)) + } + serializedSchain.WriteByte(',') + + if node.HP != nil { + // node.HP is integer pointer so 1st dereference it then convert it to string and push to serializedSchain + fmt.Fprintf(&serializedSchain, "%d", *node.HP) + } + serializedSchain.WriteByte(',') + + if node.RID != "" { + serializedSchain.WriteString(url.QueryEscape(node.RID)) + } + serializedSchain.WriteByte(',') + + if node.Name != "" { + serializedSchain.WriteString(url.QueryEscape(node.Name)) + } + serializedSchain.WriteByte(',') + + if node.Domain != "" { + serializedSchain.WriteString(url.QueryEscape(node.Domain)) + } + if node.Ext != nil { + serializedSchain.WriteByte(',') + serializedSchain.WriteString(url.QueryEscape(string(node.Ext))) + } + } + return serializedSchain.String() +} diff --git a/openrtb_ext/supplyChain_test.go b/openrtb_ext/supplyChain_test.go index 728fbc68cc4..52285ba5043 100644 --- a/openrtb_ext/supplyChain_test.go +++ b/openrtb_ext/supplyChain_test.go @@ -81,3 +81,214 @@ func TestCloneSupplyChain(t *testing.T) { }) } } + +func TestSerializeSupplyChain(t *testing.T) { + type args struct { + schain *openrtb2.SupplyChain + } + tests := []struct { + name string + args args + want string + }{ + { + name: "single hop - chain complete", + args: args{schain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "exchange1.com", + SID: "1234", + RID: "bid-request-1", + Name: "publisher", + Domain: "publisher.com", + HP: openrtb2.Int8Ptr(1), + }, + }}}, + want: "1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com", + }, + { + name: "multiple hops - with all properties supplied", + args: args{schain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "exchange1.com", + SID: "1234", + HP: openrtb2.Int8Ptr(1), + RID: "bid-request-1", + Name: "publisher", + Domain: "publisher.com", + }, + { + ASI: "exchange2.com", + SID: "abcd", + HP: openrtb2.Int8Ptr(1), + RID: "bid-request-2", + Name: "intermediary", + Domain: "intermediary.com", + }, + }}}, + want: "1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com!exchange2.com,abcd,1,bid-request-2,intermediary,intermediary.com", + }, + { + name: "single hop - chain incomplete", + args: args{schain: &openrtb2.SupplyChain{ + Complete: 0, + Ver: "1.0", + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "exchange1.com", + SID: "1234", + RID: "bid-request-1", + Name: "publisher", + HP: openrtb2.Int8Ptr(1), + }, + }}}, + want: "1.0,0!exchange1.com,1234,1,bid-request-1,publisher,", + }, + { + name: "single hop - chain complete, encoded values", + args: args{schain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "exchange1.com", + SID: "1234!abcd", + HP: openrtb2.Int8Ptr(1), + RID: "bid-request-1", + Name: "publisher, Inc.", + Domain: "publisher.com", + }, + }}}, + want: "1.0,1!exchange1.com,1234%21abcd,1,bid-request-1,publisher%2C+Inc.,publisher.com", + }, + { + name: "multiple hops - with all properties supplied,encoded values", + args: args{schain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "exchange1.com", + SID: "1234&abcd", + HP: openrtb2.Int8Ptr(1), + RID: "bid-request-1", + Name: "publisher?name", + Domain: "publisher.com", + }, + { + ASI: "exchange2.com", + SID: "abcd?12345", + HP: openrtb2.Int8Ptr(1), + RID: "bid-request-2", + Name: "intermediary", + Domain: "intermediary.com", + }, + }}}, + want: "1.0,1!exchange1.com,1234%26abcd,1,bid-request-1,publisher%3Fname,publisher.com!exchange2.com,abcd%3F12345,1,bid-request-2,intermediary,intermediary.com", + }, + { + name: "single hop - chain complete, missing optional field - encoded values", + args: args{schain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "exchange1.com", + SID: "1234&&abcd", + HP: openrtb2.Int8Ptr(1), + RID: "bid-request-1", + Name: "publisher, Inc.", + }, + }}}, + want: "1.0,1!exchange1.com,1234%26%26abcd,1,bid-request-1,publisher%2C+Inc.,", + }, + { + name: "single hop - chain complete, missing domain field - encoded values", + args: args{schain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "exchange1.com", + SID: "1234!abcd", + HP: openrtb2.Int8Ptr(1), + RID: "bid-request-1", + Name: "publisher, Inc.", + }, + }}}, + want: "1.0,1!exchange1.com,1234%21abcd,1,bid-request-1,publisher%2C+Inc.,", + }, + { + name: "single hop with extension - chain complete", + args: args{schain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "exchange1.com", + SID: "1234", + RID: "bid-request-1", + Name: "publisher", + Domain: "publisher.com", + HP: openrtb2.Int8Ptr(1), + Ext: []byte(`{"test":1,"url":"https://testuser.com?k1=v1&k2=v2"}`), + }, + }}}, + want: "1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com,%7B%22test%22%3A1%2C%22url%22%3A%22https%3A%2F%2Ftestuser.com%3Fk1%3Dv1%26k2%3Dv2%22%7D", + }, + { + name: "single hop with encoded extension - chain complete", + args: args{schain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "exchange1.com", + SID: "1234", + RID: "bid-request-1", + Name: "publisher", + Domain: "publisher.com", + HP: openrtb2.Int8Ptr(1), + Ext: []byte(`%7B%22test%22%3A1%7D`), + }, + }}}, + want: "1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com,%257B%2522test%2522%253A1%257D", + }, + { + name: "multiple hops with extension - some optional properties not supplied", + args: args{schain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "exchange1.com", + SID: "1234", + HP: openrtb2.Int8Ptr(1), + RID: "bid-request-1", + Domain: "publisher.com", + Ext: []byte(`{"test1":1,"name":"test","age":22}`), + }, + { + ASI: "exchange2.com", + SID: "abcd", + HP: openrtb2.Int8Ptr(1), + RID: "bid-request-2", + Name: "intermediary", + Ext: []byte(`{"test"}`), + }, + }}}, + want: "1.0,1!exchange1.com,1234,1,bid-request-1,,publisher.com,%7B%22test1%22%3A1%2C%22name%22%3A%22test%22%2C%22age%22%3A22%7D!exchange2.com,abcd,1,bid-request-2,intermediary,,%7B%22test%22%7D", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := SerializeSupplyChain(tt.args.schain) + assert.Equal(t, tt.want, got, tt.name) + }) + } +} diff --git a/openrtb_ext/user.go b/openrtb_ext/user.go index 19c6bbc9a6c..586c0aba74b 100644 --- a/openrtb_ext/user.go +++ b/openrtb_ext/user.go @@ -1,6 +1,7 @@ package openrtb_ext import ( + "encoding/json" "strconv" "strings" @@ -20,6 +21,8 @@ type ExtUser struct { Prebid *ExtUserPrebid `json:"prebid,omitempty"` Eids []openrtb2.EID `json:"eids,omitempty"` + + Data json.RawMessage `json:"data,omitempty"` } // ExtUserPrebid defines the contract for bidrequest.user.ext.prebid diff --git a/router/router.go b/router/router.go index d89d1f59ca2..49f0fd9c4dc 100644 --- a/router/router.go +++ b/router/router.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/json" "fmt" + "net" "net/http" "os" "strings" @@ -28,6 +29,7 @@ import ( metricsConf "github.com/prebid/prebid-server/v2/metrics/config" "github.com/prebid/prebid-server/v2/modules" "github.com/prebid/prebid-server/v2/modules/moduledeps" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/pbs" pbc "github.com/prebid/prebid-server/v2/prebid_cache_client" @@ -150,6 +152,14 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R MaxIdleConnsPerHost: cfg.Client.MaxIdleConnsPerHost, IdleConnTimeout: time.Duration(cfg.Client.IdleConnTimeout) * time.Second, TLSClientConfig: &tls.Config{RootCAs: certPool}, + + TLSHandshakeTimeout: time.Duration(cfg.Client.TLSHandshakeTimeout) * time.Second, + ResponseHeaderTimeout: time.Duration(cfg.Client.ResponseHeaderTimeout) * time.Second, + + Dial: (&net.Dialer{ + Timeout: time.Duration(cfg.Client.DialTimeout) * time.Millisecond, + KeepAlive: time.Duration(cfg.Client.DialKeepAlive) * time.Second, + }).Dial, }, } @@ -181,6 +191,8 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R if len(errs) > 0 { return nil, errortypes.NewAggregateError("user sync", errs) } + // set the syncerMap for pubmatic ow module + models.SetSyncerMap(syncersByBidder) syncerKeys := make([]string, 0, len(syncersByBidder)) syncerKeysHashSet := map[string]struct{}{} @@ -191,18 +203,21 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R syncerKeys = append(syncerKeys, k) } - moduleDeps := moduledeps.ModuleDeps{HTTPClient: generalHttpClient, RateConvertor: rateConvertor} + metricsRegistry := metricsConf.NewMetricsRegistry() + moduleDeps := moduledeps.ModuleDeps{HTTPClient: generalHttpClient, MetricsCfg: &cfg.Metrics, MetricsRegistry: metricsRegistry, RateConvertor: rateConvertor} repo, moduleStageNames, err := modules.NewBuilder().Build(cfg.Hooks.Modules, moduleDeps) if err != nil { glog.Fatalf("Failed to init hook modules: %v", err) } // Metrics engine - r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, openrtb_ext.CoreBidderNames(), syncerKeys, moduleStageNames) + r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, metricsRegistry, openrtb_ext.CoreBidderNames(), syncerKeys, moduleStageNames) shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher, storedRespFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) // todo(zachbadgett): better shutdown r.Shutdown = shutdown + priceFloorFetcher := floors.NewPriceFloorFetcher(cfg.PriceFloors, floorFechterHttpClient, r.MetricsEngine) + analyticsRunner := analyticsBuild.New(&cfg.Analytics) paramsValidator, err := openrtb_ext.NewBidderParamsValidator(schemaDirectory) @@ -223,6 +238,14 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R gdprPermsBuilder := gdpr.NewPermissionsBuilder(cfg.GDPR, gvlVendorIDs, vendorListFetcher) tcf2CfgBuilder := gdpr.NewTCF2Config + if cfg.VendorListScheduler.Enabled { + vendorListScheduler, err := gdpr.GetVendorListScheduler(cfg.VendorListScheduler.Interval, cfg.VendorListScheduler.Timeout, generalHttpClient) + if err != nil { + glog.Fatal(err) + } + vendorListScheduler.Start() + } + cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine) adapters, adaptersErrs := exchange.BuildAdapters(generalHttpClient, cfg, cfg.BidderInfos, r.MetricsEngine) @@ -235,7 +258,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R glog.Fatalf("Failed to create ads cert signer: %v", err) } - priceFloorFetcher := floors.NewPriceFloorFetcher(cfg.PriceFloors, floorFechterHttpClient, r.MetricsEngine) + // priceFloorFetcher := floors.NewPriceFloorFetcher(cfg.PriceFloors, floorFechterHttpClient, r.MetricsEngine) tmaxAdjustments := exchange.ProcessTMaxAdjustments(cfg.TmaxAdjustments) planBuilder := hooks.NewExecutionPlanBuilder(cfg.Hooks, repo) @@ -296,6 +319,28 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R r.POST("/optout", userSyncDeps.OptOut) r.GET("/optout", userSyncDeps.OptOut) + r.registerOpenWrapEndpoints(openrtbEndpoint, ampEndpoint) + + g_syncers = syncersByBidder + g_metrics = r.MetricsEngine + g_cfg = cfg + g_storedReqFetcher = &fetcher + g_accounts = &accounts + g_videoFetcher = &videoFetcher + g_storedRespFetcher = &storedRespFetcher + g_analytics = &analyticsRunner + g_paramsValidator = ¶msValidator + g_activeBidders = activeBidders + g_disabledBidders = disabledBidders + g_defReqJSON = defReqJSON + g_cacheClient = &cacheClient + g_ex = &theExchange + g_gdprPermsBuilder = gdprPermsBuilder + g_tcf2CfgBuilder = tcf2CfgBuilder + g_planBuilder = &planBuilder + g_currencyConversions = rateConvertor.Rates() + g_tmaxAdjustments = tmaxAdjustments + return r, nil } diff --git a/router/router_ow.go b/router/router_ow.go new file mode 100644 index 00000000000..18705d524a0 --- /dev/null +++ b/router/router_ow.go @@ -0,0 +1,37 @@ +package router + +import ( + "net/http" + + "github.com/julienschmidt/httprouter" +) + +const ( + OpenWrapAuction = "/pbs/openrtb2/auction" + OpenWrapV25 = "/openrtb/2.5" + OpenWrapV25Video = "/openrtb/2.5/video" + OpenWrapAmp = "/openrtb/amp" + OpenWrapHealthcheck = "/healthcheck" +) + +// Support legacy APIs for a grace period. +// not implementing middleware to avoid duplicate processing like read, unmarshal, write, etc. +// handling the temporary middleware stuff in EntryPoint hook. +func (r *Router) registerOpenWrapEndpoints(openrtbEndpoint, ampEndpoint httprouter.Handle) { + //OpenWrap hybrid + r.POST(OpenWrapAuction, openrtbEndpoint) + + // OpenWrap 2.5 in-app, etc + r.POST(OpenWrapV25, openrtbEndpoint) + + // OpenWrap 2.5 video + r.GET(OpenWrapV25Video, openrtbEndpoint) + + // OpenWrap AMP + r.POST(OpenWrapAmp, ampEndpoint) + + // healthcheck used by k8s + r.GET(OpenWrapHealthcheck, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + w.WriteHeader(http.StatusOK) + }) +} diff --git a/router/router_sshb.go b/router/router_sshb.go new file mode 100644 index 00000000000..19a0b150f25 --- /dev/null +++ b/router/router_sshb.go @@ -0,0 +1,141 @@ +package router + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/hooks" + + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/endpoints" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/metrics" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + pbc "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/uuidutil" + "github.com/prometheus/client_golang/prometheus" +) + +// TODO: Delete router_sshb.go usage after PBS-OpenWrap module + +var ( + g_syncers map[string]usersync.Syncer + g_cfg *config.Configuration + g_ex *exchange.Exchange + g_accounts *stored_requests.AccountFetcher + g_paramsValidator *openrtb_ext.BidderParamValidator + g_storedReqFetcher *stored_requests.Fetcher + g_storedRespFetcher *stored_requests.Fetcher + g_metrics metrics.MetricsEngine + g_analytics *analytics.Runner + g_disabledBidders map[string]string + g_videoFetcher *stored_requests.Fetcher + g_activeBidders map[string]openrtb_ext.BidderName + g_defReqJSON []byte + g_cacheClient *pbc.Client + g_gdprPermsBuilder gdpr.PermissionsBuilder + g_tcf2CfgBuilder gdpr.TCF2ConfigBuilder + g_planBuilder *hooks.ExecutionPlanBuilder + g_currencyConversions currency.Conversions + g_tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed +) + +func GetCacheClient() *pbc.Client { + return g_cacheClient +} + +func GetPrebidCacheURL() string { + return g_cfg.ExternalURL +} + +// RegisterAnalyticsModule function registers the PBSAnalyticsModule +func RegisterAnalyticsModule(anlt analytics.Module) error { + if g_analytics == nil { + return fmt.Errorf("g_analytics is nil") + } + modules, err := analyticsBuild.EnableAnalyticsModule(anlt, *g_analytics) + if err != nil { + return err + } + g_analytics = &modules + return nil +} + +// OrtbAuctionEndpointWrapper Openwrap wrapper method for calling /openrtb2/auction endpoint +func OrtbAuctionEndpointWrapper(w http.ResponseWriter, r *http.Request) error { + ortbAuctionEndpoint, err := openrtb2.NewEndpoint(uuidutil.UUIDRandomGenerator{}, *g_ex, *g_paramsValidator, *g_storedReqFetcher, *g_accounts, g_cfg, g_metrics, *g_analytics, g_disabledBidders, g_defReqJSON, g_activeBidders, *g_storedRespFetcher, *g_planBuilder, g_tmaxAdjustments) + if err != nil { + return err + } + ortbAuctionEndpoint(w, r, nil) + return nil +} + +// GetPBSCurrencyRate Openwrap wrapper method for currency conversion +func GetPBSCurrencyConversion(from, to string, value float64) (float64, error) { + rate, err := g_currencyConversions.GetRate(from, to) + if err == nil { + return value * rate, nil + } + return 0, err +} + +// VideoAuctionEndpointWrapper Openwrap wrapper method for calling /openrtb2/video endpoint +func VideoAuctionEndpointWrapper(w http.ResponseWriter, r *http.Request) error { + videoAuctionEndpoint, err := openrtb2.NewCTVEndpoint(*g_ex, *g_paramsValidator, *g_storedReqFetcher, *g_videoFetcher, *g_accounts, g_cfg, g_metrics, *g_analytics, g_disabledBidders, g_defReqJSON, g_activeBidders, *g_planBuilder, g_tmaxAdjustments) + if err != nil { + return err + } + videoAuctionEndpoint(w, r, nil) + return nil +} + +// GetUIDSWrapper Openwrap wrapper method for calling /getuids endpoint +func GetUIDSWrapper(w http.ResponseWriter, r *http.Request) { + getUID := endpoints.NewGetUIDsEndpoint(g_cfg.HostCookie) + getUID(w, r, nil) +} + +// SetUIDSWrapper Openwrap wrapper method for calling /setuid endpoint +func SetUIDSWrapper(w http.ResponseWriter, r *http.Request) { + setUID := endpoints.NewSetUIDEndpoint(g_cfg, g_syncers, g_gdprPermsBuilder, g_tcf2CfgBuilder, *g_analytics, *g_accounts, g_metrics) + setUID(w, r, nil) +} + +// CookieSync Openwrap wrapper method for calling /cookie_sync endpoint +func CookieSync(w http.ResponseWriter, r *http.Request) { + cookiesync := endpoints.NewCookieSyncEndpoint(g_syncers, g_cfg, g_gdprPermsBuilder, g_tcf2CfgBuilder, g_metrics, *g_analytics, *g_accounts, g_activeBidders) + cookiesync.Handle(w, r, nil) +} + +// SyncerMap Returns map of bidder and its usersync info +func SyncerMap() map[string]usersync.Syncer { + return g_syncers +} + +func GetPrometheusGatherer() *prometheus.Registry { + mEngine, ok := g_metrics.(*metricsConf.DetailedMetricsEngine) + if !ok || mEngine == nil || mEngine.PrometheusMetrics == nil { + return nil + } + + return mEngine.PrometheusMetrics.Gatherer +} + +// CallRecordNonBids calls RecordRejectedBids function on prebid's metric-engine +func CallRecordNonBids(pubId, bidder string, code openrtb3.NonBidStatusCode) { + if g_metrics != nil { + codeStr := strconv.FormatInt(int64(code), 10) + g_metrics.RecordRejectedBids(pubId, bidder, codeStr) + } +} diff --git a/router/router_test.go b/router/router_test.go index cc2f077e5e6..7647f8d4323 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -7,6 +7,7 @@ import ( "os" "testing" + _ "github.com/lib/pq" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/util/jsonutil" diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 640f8022f57..2f4c09fb7be 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -22,9 +22,55 @@ generate_cover_data() { for pkg in "$@"; do f="$workdir/$(echo $pkg | tr / -).cover" cover="" - if ! [[ "$pkg" =~ ^github\.com\/prebid\/prebid\-server$ ]]; then + if ! [[ "$pkg" =~ ^github\.com\/PubMatic\-OpenWrap\/prebid\-server$ ]]; then cover="-covermode=$mode -coverprofile=$f" fi + # util/task uses _test package name + if [[ "$pkg" =~ ^github\.com\/PubMatic\-OpenWrap\/prebid\-server\/util\/task$ ]]; then + cover+=" -coverpkg=github.com/prebid/prebid-server/v2/util/task" + fi + + if [[ "$pkg" =~ ^github\.com\/PubMatic\-OpenWrap\/prebid\-server\/router$ ]]; then + cover+=" -coverpkg=github.com/prebid/prebid-server/v2/router" + fi + + # temporarily disable openwrap, remove as we add full support to each package + if [[ "$pkg" =~ ^github\.com\/PubMatic\-OpenWrap\/prebid\-server\/modules\/pubmatic\/openwrap$ ]]; then + cover+=" -coverpkg=github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap" + fi + + if [[ "$pkg" =~ ^github\.com\/PubMatic\-OpenWrap\/prebid\-server\/modules\/pubmatic\/openwrap\/adapters$ ]]; then + cover+=" -coverpkg=github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adapters" + fi + + if [[ "$pkg" =~ ^github\.com\/PubMatic\-OpenWrap\/prebid\-server\/modules\/pubmatic\/openwrap\/adunitconfig$ ]]; then + cover+=" -coverpkg=github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adunitconfig" + fi + + if [[ "$pkg" =~ ^github\.com\/PubMatic\-OpenWrap\/prebid\-server\/modules\/pubmatic\/openwrap\/bidderparams$ ]]; then + cover+=" -coverpkg=github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/bidderparams" + fi + + if [[ "$pkg" =~ ^github\.com\/PubMatic\-OpenWrap\/prebid\-server\/modules\/pubmatic\/openwrap\/config$ ]]; then + cover+=" -coverpkg=github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + fi + + if [[ "$pkg" =~ ^github\.com\/PubMatic\-OpenWrap\/prebid\-server\/modules\/pubmatic\/openwrap\/database$ ]]; then + cover+=" -coverpkg=github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/database" + fi + + if [[ "$pkg" =~ ^github\.com\/PubMatic\-OpenWrap\/prebid\-server\/modules\/pubmatic\/openwrap\/metrics$ ]]; then + cover+=" -coverpkg=github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + fi + + if [[ "$pkg" =~ ^github\.com\/PubMatic\-OpenWrap\/prebid\-server\/modules\/pubmatic\/openwrap\/metrics\/stats$ ]]; then + cover+=" -coverpkg=github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/stats" + fi + + if [[ "$pkg" =~ ^github\.com\/PubMatic\-OpenWrap\/prebid\-server\/modules\/pubmatic\/openwrap\/models$ ]]; then + cover+=" -coverpkg=github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + fi + go test ${cover} "$pkg" done diff --git a/scripts/upgrade-pbs.sh b/scripts/upgrade-pbs.sh new file mode 100755 index 00000000000..e17eb6b99e8 --- /dev/null +++ b/scripts/upgrade-pbs.sh @@ -0,0 +1,262 @@ +#!/bin/bash -e + +prefix="v" +to_major=0 +to_minor=259 +to_patch=0 +upgrade_version="$prefix$to_major.$to_minor.$to_patch" + +attempt=1 + +usage=" +Script starts or continues prebid upgrade to version set in 'to_minor' variable. Workspace is at /tmp/prebid-server and /tmp/pbs-patch + + ./upgrade-pbs.sh [--restart] + + --restart Restart the upgrade (deletes /tmp/prebid-server and /tmp/pbs-patch) + -h Help + +TODO: + - paramertrize the script + - create ci branch PR + - create header-bidding PR" + +RESTART=0 +for i in "$@"; do + case $i in + --restart) + RESTART=1 + shift + ;; + -h) + echo "$usage" + exit 0 + ;; + esac +done + +# --- start --- +CHECKLOG=/tmp/pbs-patch/checkpoints.log + +trap 'clear_log' EXIT + +log () { + printf "\n$(date): $1\n" +} + +clear_log() { + major=0 + minor=0 + patch=0 + get_current_tag_version major minor patch + current_fork_at_version="$major.$minor.$patch" + + if [ "$current_fork_at_version" == "$upgrade_version" ] ; then + log "Upgraded to $current_fork_at_version" + rm -f "$CHECKLOG" + + log "Last validation before creating PR" + go_mod + checkpoint_run "./validate.sh --race 5" + go_discard + + set +e + log "Commit final go.mod and go.sum" + git commit go.mod go.sum --amend --no-edit + set -e + else + log "Exiting with failure!!!" + exit 1 + fi +} + +get_current_tag_version() { + log "get_current_tag_version $*" + + local -n _major=$1 + local -n _minor=$2 + local -n _patch=$3 + + # script will always start from start if origin/master is used. + # common_commit=$(git merge-base prebid-upstream/master origin/master) + # log "Common commit b/w prebid-upstream/master origin/master: $common_commit" + + # remove origin for master to continue from last fixed tag's rebase. + common_commit=$(git merge-base prebid-upstream/master master) + log "Common commit b/w prebid-upstream/master master: $common_commit" + + current_version=$(git tag --points-at $common_commit) + if [[ $current_version == v* ]] ; then + log "Current Version: $current_version" + else + log "Failed to detected current version. Abort." + exit 1 + # abort + # cd prebid-server; git rebase --abort;cd - + fi + + IFS='.' read -r -a _current_version <<< "$current_version" + _major=${_current_version[0]} + _minor=${_current_version[1]} + _patch=${_current_version[2]} +} + +clone_repo() { + if [ -d "/tmp/prebid-server" ]; then + log "Code already cloned. Attempting to continue the upgrade!!!" + else + log "Cloning repo at /tmp" + cd /tmp + git clone https://github.com/PubMatic-OpenWrap/prebid-server.git + cd prebid-server + + git remote add prebid-upstream https://github.com/prebid/prebid-server.git + git remote -v + git fetch --all --tags --prune + fi +} + +checkout_branch() { + set +e + git checkout tags/$_upgrade_version -b $tag_base_branch_name + # git push origin $tag_base_branch_name + + git checkout -b $upgrade_branch_name + # git push origin $upgrade_branch_name + + set -e +# if [ "$?" -ne 0 ] +# then +# log "Failed to create branch $upgrade_branch_name. Already working on it???" +# exit 1 +# fi +} + +cmd_exe() { + cmd=$* + if ! $cmd; then + log "Failure!!! creating checkpoint $cmd" + echo "$cmd" > $CHECKLOG + exit 1 + fi +} + +checkpoint_run() { + cmd=$* + if [ -f $CHECKLOG ] ; then + if grep -q "$cmd" "$CHECKLOG"; then + log "Retry this checkpoint: $cmd" + rm "$CHECKLOG" + elif grep -q "./validate.sh --race 5" "$CHECKLOG"; then + log "Special checkpoint. ./validate.sh --race 5 failed for last tag update. Hence, only fixes are expected in successfully upgraded branch. (change in func() def, wrong conflict resolve, etc)" + cmd_exe $cmd + rm "$CHECKLOG" + else + log "Skip this checkpoint: $cmd" + return + fi + fi + cmd_exe $cmd +} + +go_mod() { + go mod download all + go mod tidy + go mod tidy + go mod download all +} + +go_discard() { + # discard local changes if any. manual validate, compile, etc + # git checkout master go.mod + # git checkout master go.sum + git checkout go.mod go.sum +} + +# --- main --- + +if [ "$RESTART" -eq "1" ]; then + log "Restarting the upgrade: rm -rf /tmp/prebid-server /tmp/pbs-patch/" + rm -rf /tmp/prebid-server /tmp/pbs-patch/ + mkdir -p /tmp/pbs-patch/ +fi + +log "Final Upgrade Version: $upgrade_version" +log "Attempt: $attempt" + +checkpoint_run clone_repo +cd /tmp/prebid-server +log "At $(pwd)" + +# code merged in master +# if [ "$RESTART" -eq "1" ]; then +# # TODO: commit this in origin/master,ci and remove it from here. +# git merge --squash origin/UOE-7610-1-upgrade.sh +# git commit --no-edit +# fi + +major=0 +minor=0 +patch=0 + +get_current_tag_version major minor patch +current_fork_at_version="$major.$minor.$patch" +git diff tags/$current_fork_at_version..origin/master > /tmp/pbs-patch/current_ow_patch-$current_fork_at_version-origin_master-$attempt.diff + +((minor++)) +log "Starting with version split major:$major, minor:$minor, patch:$patch" + +# how to validate with this code +# if [ "$RESTART" -eq "1" ]; then +# # Solving go.mod and go.sum conflicts would be easy at last as we would need to only pick the OW-patch entries rather than resolving conflict for every version +# log "Using latest go.mod and go.sum. Patch OW changes at last" +# git checkout tags/$current_fork_at_version go.mod +# git checkout tags/$current_fork_at_version go.sum +# git commit go.mod go.sum -m "[upgrade-start-checkpoint] tags/$current_fork_at_version go.mod go.sum" +# fi + +log "Checking if last failure was for test case. Need this to pick correct" +go_mod +checkpoint_run "./validate.sh --race 5" +go_discard + +log "Starting upgrade loop..." +while [ "$minor" -le "$to_minor" ]; do + # _upgrade_version="$prefix$major.$minor.$patch" + _upgrade_version="$major.$minor.$patch" + ((minor++)) + + log "Starting upgrade to version $_upgrade_version" + + tag_base_branch_name=prebid_$_upgrade_version-$attempt-tag + upgrade_branch_name=prebid_$_upgrade_version-$attempt + log "Reference tag branch: $tag_base_branch_name" + log "Upgrade branch: $upgrade_branch_name" + + checkpoint_run checkout_branch + + checkpoint_run git merge master --no-edit + # Use `git commit --amend --no-edit` if you had to fix test cases, etc for wrong merge conflict resolve, etc. + log "Validating the master merge into current tag. Fix and commit changes if required. Use 'git commit --amend --no-edit' for consistency" + go_mod + checkpoint_run "./validate.sh --race 5" + go_discard + + checkpoint_run git checkout master + checkpoint_run git merge $upgrade_branch_name --no-edit + + log "Generating patch file at /tmp/pbs-patch/ for $_upgrade_version" + git diff tags/$_upgrade_version..master > /tmp/pbs-patch/new_ow_patch_$upgrade_version-master-1.diff +done + +# TODO: +# diff tags/v0.192.0..origin/master +# diff tags/v0.207.0..prebid_v0.207.0 + +# TODO: UPDATE HEADER-BIDDING GO-MOD + + +# TODO: automate go.mod conflicts +# go mod edit -replace github.com/prebid/prebid-server=./ +# go mod edit -replace github.com/mxmCherry/openrtb/v16=github.com/PubMatic-OpenWrap/openrtb/v15@v15.0.0 +# go mod edit -replace github.com/beevik/etree=github.com/PubMatic-OpenWrap/etree@latest diff --git a/static/bidder-info/spotx.yaml b/static/bidder-info/spotx.yaml new file mode 100644 index 00000000000..51824561022 --- /dev/null +++ b/static/bidder-info/spotx.yaml @@ -0,0 +1,10 @@ +maintainer: + email: "teameighties@spotx.tv" +gvlVendorID: 165 +capabilities: + app: + mediaTypes: + - video + site: + mediaTypes: + - video diff --git a/static/bidder-info/vastbidder.yaml b/static/bidder-info/vastbidder.yaml new file mode 100644 index 00000000000..b8eb41d4e49 --- /dev/null +++ b/static/bidder-info/vastbidder.yaml @@ -0,0 +1,9 @@ +maintainer: + email: "UOEDev@pubmatic.com" +capabilities: + app: + mediaTypes: + - video + site: + mediaTypes: + - video diff --git a/static/bidder-params/spotx.json b/static/bidder-params/spotx.json new file mode 100644 index 00000000000..13b72f2156b --- /dev/null +++ b/static/bidder-params/spotx.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "OpenX Adapter Params", + "description": "A schema which validates params accepted by the OpenX adapter", + + "type": "object", + "properties": { + "channel_id": { + "type": "string", + "minLength": 5, + "maxLength": 5, + "description": "A unique 5 digit ID that is generated by the SpotX publisher platform when a channel is created" + }, + "ad_unit": { + "type": "string", + "description": "Token that describes which ad unit to play: instream or outstream", + "enum": ["instream", "outstream"] + }, + "secure": { + "type": "boolean", + "description": "Boolean identifying whether the reqeusts should be https or not (used to override the protocol if the page isn’t secure." + }, + "ad_volume": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Value between 0 and 1 to denote the volume the ad should start at." + }, + "price_floor": { + "type": "integer", + "description": "Set the current channel price floor in real time." + }, + "hide_skin": { + "type": "boolean", + "description": "Set to true to hide the spotx skin." + } + }, + "required": ["channel_id", "ad_unit"] +} diff --git a/static/bidder-params/vastbidder.json b/static/bidder-params/vastbidder.json new file mode 100644 index 00000000000..0bef9b5fd5e --- /dev/null +++ b/static/bidder-params/vastbidder.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Tag Bidder Base Adapter", + "description": "A schema which validates params accepted by the VAST tag bidders", + + "type": "object", + "properties": { + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tagid": { "type": "string" }, + "url": { "type": "string" }, + "dur": { "type": "integer" }, + "price": { "type": "number" }, + "params": { "type": "object" } + }, + "required": [ "tagid", "url", "dur" ] + } + }, + "parser": { "type": "string" }, + "headers": { "type": "object" }, + "cookies": { "type": "object" } + }, + "required": ["tags"] +} \ No newline at end of file diff --git a/usersync/cookie.go b/usersync/cookie.go index b4bc821c9d7..879a338ee64 100644 --- a/usersync/cookie.go +++ b/usersync/cookie.go @@ -99,13 +99,14 @@ func WriteCookie(w http.ResponseWriter, encodedCookie string, cfg *config.HostCo Expires: time.Now().Add(ttl), Path: "/", } + httpCookie.Secure = true // pbs_upgrade: set secure to true without checking setSiteCookie flag if cfg.Domain != "" { httpCookie.Domain = cfg.Domain } if setSiteCookie { - httpCookie.Secure = true + // httpCookie.Secure = true httpCookie.SameSite = http.SameSiteNoneMode } diff --git a/util/boolutil/boolutil.go b/util/boolutil/boolutil.go new file mode 100644 index 00000000000..bc83569d035 --- /dev/null +++ b/util/boolutil/boolutil.go @@ -0,0 +1,6 @@ +package boolutil + +// BoolPtr returns pointer value of boolean input +func BoolPtr(val bool) *bool { + return &val +} diff --git a/version/xprebidheader.go b/version/xprebidheader.go index 613a76d80b6..ee0f53e737a 100644 --- a/version/xprebidheader.go +++ b/version/xprebidheader.go @@ -8,7 +8,7 @@ import ( "github.com/prebid/prebid-server/v2/openrtb_ext" ) -const xPrebidHeaderVersionPrefix = "pbs-go" +const xPrebidHeaderVersionPrefix = "owpbs-go" func BuildXPrebidHeader(version string) string { sb := &strings.Builder{} diff --git a/version/xprebidheader_test.go b/version/xprebidheader_test.go index db31ea63620..51ba63640b2 100644 --- a/version/xprebidheader_test.go +++ b/version/xprebidheader_test.go @@ -19,12 +19,12 @@ func TestBuildXPrebidHeader(t *testing.T) { { description: "No Version", version: "", - result: "pbs-go/unknown", + result: "owpbs-go/unknown", }, { description: "Version", version: "0.100.0", - result: "pbs-go/0.100.0", + result: "owpbs-go/0.100.0", }, } @@ -45,12 +45,12 @@ func TestBuildXPrebidHeaderForRequest(t *testing.T) { { description: "No versions", version: "", - result: "pbs-go/unknown", + result: "owpbs-go/unknown", }, { description: "pbs", version: "test-version", - result: "pbs-go/test-version", + result: "owpbs-go/test-version", }, { description: "prebid.js", @@ -63,7 +63,7 @@ func TestBuildXPrebidHeaderForRequest(t *testing.T) { }, }, }, - result: "pbs-go/test-version,pbjs/test-pbjs-version", + result: "owpbs-go/test-version,pbjs/test-pbjs-version", }, { description: "unknown prebid.js", @@ -75,7 +75,7 @@ func TestBuildXPrebidHeaderForRequest(t *testing.T) { }, }, }, - result: "pbs-go/test-version,pbjs/unknown", + result: "owpbs-go/test-version,pbjs/unknown", }, { description: "channel without a name", @@ -87,7 +87,7 @@ func TestBuildXPrebidHeaderForRequest(t *testing.T) { }, }, }, - result: "pbs-go/test-version", + result: "owpbs-go/test-version", }, { description: "prebid-mobile", @@ -98,7 +98,7 @@ func TestBuildXPrebidHeaderForRequest(t *testing.T) { Version: "test-prebid-mobile-version", }, }, - result: "pbs-go/test-version,prebid-mobile/test-prebid-mobile-version", + result: "owpbs-go/test-version,prebid-mobile/test-prebid-mobile-version", }, { description: "app ext without a source", @@ -108,7 +108,7 @@ func TestBuildXPrebidHeaderForRequest(t *testing.T) { Version: "test-version", }, }, - result: "pbs-go/test-version", + result: "owpbs-go/test-version", }, { description: "Version found in both req.Ext and req.App.Ext", @@ -127,7 +127,7 @@ func TestBuildXPrebidHeaderForRequest(t *testing.T) { Version: "test-prebid-mobile-version", }, }, - result: "pbs-go/test-version,pbjs/test-pbjs-version,prebid-mobile/test-prebid-mobile-version", + result: "owpbs-go/test-version,pbjs/test-pbjs-version,prebid-mobile/test-prebid-mobile-version", }, } From 7067a78a2cba021e89009b03df4c1c19f4b19393 Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Wed, 13 Dec 2023 11:22:36 +0530 Subject: [PATCH 132/138] UT Fix TestInitiateReloader --- .../fullscreenclickability/reloader_test.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/modules/pubmatic/openwrap/fullscreenclickability/reloader_test.go b/modules/pubmatic/openwrap/fullscreenclickability/reloader_test.go index c81b4a28fef..98217264974 100644 --- a/modules/pubmatic/openwrap/fullscreenclickability/reloader_test.go +++ b/modules/pubmatic/openwrap/fullscreenclickability/reloader_test.go @@ -2,7 +2,6 @@ package fullscreenclickability import ( "testing" - "time" "github.com/golang/mock/gomock" cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" @@ -24,35 +23,41 @@ func TestInitiateReloader(t *testing.T) { cache cache.Cache } + waitChan := make(chan struct{}, 1) tests := []struct { name string args args - setup func() + setup func(chan struct{}) }{ { name: "test InitateReloader with valid cache and invalid time, exit", args: args{defaultExpiry: 0, cache: mockCache, }, - setup: func() {}, + setup: func(w chan struct{}) { + w <- struct{}{} + }, }, { name: "test InitateReloader with valid cache and time, call once and exit", args: args{defaultExpiry: 1000, cache: mockCache, }, - setup: func() { + setup: func(w chan struct{}) { mockCache.EXPECT().GetFSCDisabledPublishers().Return(map[int]struct{}{}, nil) - mockCache.EXPECT().GetFSCThresholdPerDSP().Return(map[int]int{}, nil) + mockCache.EXPECT().GetFSCThresholdPerDSP().Do(func() { + w <- struct{}{} + }).Return(map[int]int{}, nil) }, }, } for _, tt := range tests { - tt.setup() + tt.setup(waitChan) fscConfigs.serviceStop = make(chan struct{}) go initiateReloader(tt.args.cache, tt.args.defaultExpiry) + //wait + <-waitChan //closing channel to avoid infinite loop StopReloaderService() - time.Sleep(1 * time.Millisecond) } } From c05ba76721852f077f5a72387e530982c3dedd7e Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Fri, 15 Dec 2023 15:06:24 +0530 Subject: [PATCH 133/138] Added missing code --- exchange/bidder.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index de2ce294790..389ee6685e0 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -356,7 +356,10 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if err == nil { // Conversion rate found, using it for conversion for i := 0; i < len(bidResponse.Bids); i++ { - + if bidResponse.Bids[i].BidMeta == nil { + bidResponse.Bids[i].BidMeta = &openrtb_ext.ExtBidPrebidMeta{} + } + bidResponse.Bids[i].BidMeta.AdapterCode = bidderRequest.BidderName.String() bidderName := bidderRequest.BidderName if bidResponse.Bids[i].Seat != "" { bidderName = bidResponse.Bids[i].Seat From 823227a47de391d9112c7eb55902f29b39f68382 Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Fri, 15 Dec 2023 15:29:25 +0530 Subject: [PATCH 134/138] UT Fix --- exchange/bidder_test.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index fd868118191..fb353c25e26 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -2520,6 +2520,7 @@ func TestExtraBid(t *testing.T) { DealPriority: 5, BidType: openrtb_ext.BidTypeVideo, OriginalBidCur: "USD", + BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2531,6 +2532,7 @@ func TestExtraBid(t *testing.T) { DealPriority: 4, BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", + BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2628,6 +2630,7 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { DealPriority: 5, BidType: openrtb_ext.BidTypeVideo, OriginalBidCur: "USD", + BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm-allowed", Currency: "USD", @@ -2639,6 +2642,7 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { DealPriority: 4, BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", + BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2709,7 +2713,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { }, BidType: openrtb_ext.BidTypeBanner, DealPriority: 4, - Seat: "PUBMATIC", + Seat: "pubmatic", }, { Bid: &openrtb2.Bid{ @@ -2736,6 +2740,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", + BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2751,8 +2756,9 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", OriginalBidCPM: 3, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, - Seat: "PUBMATIC", + Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", }, } @@ -2762,7 +2768,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, - BidderName: "PUBMATIC", + BidderName: openrtb_ext.BidderPubmatic, } bidAdjustments := map[string]float64{ string(openrtb_ext.BidderPubmatic): 2, // All lowercase value in bid adjustments to simulate it being case insensitive @@ -2780,7 +2786,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { openrtb_ext.ExtAlternateBidderCodes{ Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ - "PUBMATIC": { + string(openrtb_ext.BidderPubmatic): { Enabled: true, AllowedBidderCodes: []string{"groupm"}, }, @@ -2849,6 +2855,7 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", + BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2864,6 +2871,7 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", OriginalBidCPM: 3, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2962,6 +2970,7 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { OriginalBidCPM: 7, OriginalBidCur: "USD", OriginalBidCPMUSD: 7, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "INR", @@ -2978,6 +2987,7 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { OriginalBidCPM: 3, OriginalBidCur: "USD", OriginalBidCPMUSD: 3, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "INR", From dbb3b41244c89f43d8f73a214649590dd916d17f Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Mon, 18 Dec 2023 12:26:47 +0530 Subject: [PATCH 135/138] Fix: Pass through entire bid.prebid.meta --- exchange/bidder.go | 4 ---- exchange/bidder_test.go | 18 ++++-------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index 389ee6685e0..e80fea159a5 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -356,10 +356,6 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if err == nil { // Conversion rate found, using it for conversion for i := 0; i < len(bidResponse.Bids); i++ { - if bidResponse.Bids[i].BidMeta == nil { - bidResponse.Bids[i].BidMeta = &openrtb_ext.ExtBidPrebidMeta{} - } - bidResponse.Bids[i].BidMeta.AdapterCode = bidderRequest.BidderName.String() bidderName := bidderRequest.BidderName if bidResponse.Bids[i].Seat != "" { bidderName = bidResponse.Bids[i].Seat diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index fb353c25e26..fd868118191 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -2520,7 +2520,6 @@ func TestExtraBid(t *testing.T) { DealPriority: 5, BidType: openrtb_ext.BidTypeVideo, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2532,7 +2531,6 @@ func TestExtraBid(t *testing.T) { DealPriority: 4, BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2630,7 +2628,6 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { DealPriority: 5, BidType: openrtb_ext.BidTypeVideo, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm-allowed", Currency: "USD", @@ -2642,7 +2639,6 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { DealPriority: 4, BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2713,7 +2709,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { }, BidType: openrtb_ext.BidTypeBanner, DealPriority: 4, - Seat: "pubmatic", + Seat: "PUBMATIC", }, { Bid: &openrtb2.Bid{ @@ -2740,7 +2736,6 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2756,9 +2751,8 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", OriginalBidCPM: 3, - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, - Seat: string(openrtb_ext.BidderPubmatic), + Seat: "PUBMATIC", Currency: "USD", }, } @@ -2768,7 +2762,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, - BidderName: openrtb_ext.BidderPubmatic, + BidderName: "PUBMATIC", } bidAdjustments := map[string]float64{ string(openrtb_ext.BidderPubmatic): 2, // All lowercase value in bid adjustments to simulate it being case insensitive @@ -2786,7 +2780,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { openrtb_ext.ExtAlternateBidderCodes{ Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ - string(openrtb_ext.BidderPubmatic): { + "PUBMATIC": { Enabled: true, AllowedBidderCodes: []string{"groupm"}, }, @@ -2855,7 +2849,6 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2871,7 +2864,6 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", OriginalBidCPM: 3, - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2970,7 +2962,6 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { OriginalBidCPM: 7, OriginalBidCur: "USD", OriginalBidCPMUSD: 7, - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "INR", @@ -2987,7 +2978,6 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { OriginalBidCPM: 3, OriginalBidCur: "USD", OriginalBidCPMUSD: 3, - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "INR", From 9849cbabb3b1e650ac4748a9812124cfe982c67d Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Thu, 15 Feb 2024 12:32:55 +0530 Subject: [PATCH 136/138] formatting changes --- modules/pubmatic/openwrap/adapters/bidders_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/pubmatic/openwrap/adapters/bidders_test.go b/modules/pubmatic/openwrap/adapters/bidders_test.go index 7f5e872568e..2662e755d59 100644 --- a/modules/pubmatic/openwrap/adapters/bidders_test.go +++ b/modules/pubmatic/openwrap/adapters/bidders_test.go @@ -1075,7 +1075,6 @@ func TestPrepareBidParamJSONForPartnerSynacorMedia(t *testing.T) { } } - func TestPrepareBidParamJSONForPartnerGumGum(t *testing.T) { type args struct { reqID string From cc0f5271dd80e6fe40e272bc203c2541d186ffb3 Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Fri, 16 Feb 2024 13:04:56 +0530 Subject: [PATCH 137/138] Added currency conversion fix --- modules/moduledeps/deps.go | 8 ++++---- router/router.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/moduledeps/deps.go b/modules/moduledeps/deps.go index ec582a66bf0..d85cc3c6161 100644 --- a/modules/moduledeps/deps.go +++ b/modules/moduledeps/deps.go @@ -11,8 +11,8 @@ import ( // ModuleDeps provides dependencies that custom modules may need for hooks execution. // Additional dependencies can be added here if modules need something more. type ModuleDeps struct { - HTTPClient *http.Client - RateConvertor *currency.RateConverter - MetricsCfg *config.Metrics - MetricsRegistry metricsCfg.MetricsRegistry + HTTPClient *http.Client + MetricsCfg *config.Metrics + MetricsRegistry metricsCfg.MetricsRegistry + CurrencyConversion currency.Conversions } diff --git a/router/router.go b/router/router.go index 49f0fd9c4dc..aaa0e8f6188 100644 --- a/router/router.go +++ b/router/router.go @@ -204,7 +204,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R } metricsRegistry := metricsConf.NewMetricsRegistry() - moduleDeps := moduledeps.ModuleDeps{HTTPClient: generalHttpClient, MetricsCfg: &cfg.Metrics, MetricsRegistry: metricsRegistry, RateConvertor: rateConvertor} + moduleDeps := moduledeps.ModuleDeps{HTTPClient: generalHttpClient, MetricsCfg: &cfg.Metrics, MetricsRegistry: metricsRegistry, CurrencyConversion: rateConvertor.Rates()} repo, moduleStageNames, err := modules.NewBuilder().Build(cfg.Hooks.Modules, moduleDeps) if err != nil { glog.Fatalf("Failed to init hook modules: %v", err) From 4d32a334329066e7366e646ef211cfc2b61de857 Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Wed, 21 Feb 2024 09:39:13 +0530 Subject: [PATCH 138/138] Updated import to v2 version --- modules/pubmatic/openwrap/logger_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pubmatic/openwrap/logger_test.go b/modules/pubmatic/openwrap/logger_test.go index cad1a487354..b9377a0f7cc 100644 --- a/modules/pubmatic/openwrap/logger_test.go +++ b/modules/pubmatic/openwrap/logger_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" )